mirror of
https://github.com/davidalbertonogueira/MLP.git
synced 2025-12-16 20:07:07 +03:00
369 lines
12 KiB
C++
369 lines
12 KiB
C++
/**
|
|
* @file microunit.h
|
|
* @author Sebastiao Salvador de Miranda (ssm)
|
|
* @brief Tiny library for cpp unit testing. Should work on any c++11 compiler.
|
|
*
|
|
* Simply include this header in your test implementation file (e.g., main.cpp)
|
|
* and call microunit::UnitTester::Run() in the function main(). To register
|
|
* a new unit test case, use the macro UNIT (See the example below). Inside the
|
|
* test case body, you can use the following macros to control the result
|
|
* of the test.
|
|
*
|
|
* @li PASS() : Pass the test and return.
|
|
* @li FAIL() : Fail the test and return.
|
|
* @li ASSERT_TRUE(condition) : If the condition does not hold, fail and return.
|
|
* @li ASSERT_FALSE(condition) : If the condition holds, fail and return.
|
|
*
|
|
* @code{.cpp}
|
|
* UNIT(Test_Two_Plus_Two) {
|
|
* ASSERT_TRUE(2 + 2 == 4);
|
|
* };
|
|
* // ...
|
|
* int main(){
|
|
* return microunit::UnitTester::Run() ? 0 : -1;
|
|
* }
|
|
* @endcode
|
|
*
|
|
* @copyright Copyright (c) 2016, Sebastiao Salvador de Miranda.
|
|
* All rights reserved. See licence below.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* (1) Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* (2) Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* (3) The name of the author may not be used to
|
|
* endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef _MICROUNIT_MICROUNIT_H_
|
|
#define _MICROUNIT_MICROUNIT_H_
|
|
#include <string.h>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <iostream>
|
|
|
|
/**
|
|
* @brief Helper macros to get current logging filename
|
|
*/
|
|
#if defined(_WIN32)
|
|
#define __FILENAME__ (strrchr(__FILE__, '\\') ? \
|
|
strrchr(__FILE__, '\\') + 1 : __FILE__)
|
|
#else
|
|
#define __FILENAME__ (strrchr(__FILE__, '/') ? \
|
|
strrchr(__FILE__, '/') + 1 : __FILE__)
|
|
#endif
|
|
#define MICROUNIT_SEPARATOR "----------------------------------------" \
|
|
"----------------------------------------"
|
|
#if defined(_WIN32)
|
|
#include "windows.h"
|
|
#endif
|
|
|
|
namespace microunit {
|
|
const static int COLORCODE_GREY{ 7 };
|
|
const static int COLORCODE_GREEN{ 10 };
|
|
const static int COLORCODE_RED{ 12 };
|
|
const static int COLORCODE_YELLOW{ 14 };
|
|
|
|
/**
|
|
* @brief Helper class to convert from color codes to ansi escape codes
|
|
* Used to print color in non-win32 systems.
|
|
*/
|
|
std::string ColorCodeToANSI(const int color_code) {
|
|
switch (color_code) {
|
|
case COLORCODE_GREY: return "\033[22;37m";
|
|
case COLORCODE_GREEN: return "\033[01;31m";
|
|
case COLORCODE_RED: return "\033[01;32m";
|
|
case COLORCODE_YELLOW: return "\033[01;33m";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Helper function to change the current terminal color.
|
|
* @param [in] color_code Input color code.
|
|
*/
|
|
void SetTerminalColor(int color_code) {
|
|
#if defined(_WIN32)
|
|
HANDLE handler = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
CONSOLE_SCREEN_BUFFER_INFO buffer_info;
|
|
GetConsoleScreenBufferInfo(handler, &buffer_info);
|
|
SetConsoleTextAttribute(handler, ((buffer_info.wAttributes & 0xFFF0) |
|
|
(WORD)color_code));
|
|
#else
|
|
std::cout << ColorCodeToANSI(color_code);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Helper class to be used as a iostream manipulator and change the
|
|
* terminal color.
|
|
*/
|
|
class Color {
|
|
public:
|
|
Color(int code) : code_(code) {}
|
|
void Set() const {
|
|
SetTerminalColor(code_);
|
|
}
|
|
int code() const { return code_; }
|
|
private:
|
|
int code_;
|
|
};
|
|
|
|
const static Color Grey{ COLORCODE_GREY };
|
|
const static Color Green{ COLORCODE_GREEN };
|
|
const static Color Red{ COLORCODE_RED };
|
|
const static Color Yellow{ COLORCODE_YELLOW };
|
|
|
|
/**
|
|
* @brief Helper class to be used in a cout streaming statement. Resets to
|
|
* the default terminal color upon statement completion.
|
|
*/
|
|
class SaveColor {
|
|
public:
|
|
~SaveColor() {
|
|
SetTerminalColor(Grey.code());
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @brief Helper class to be used in a cout streaming statement. Puts a line
|
|
* break upon statement completion.
|
|
*/
|
|
class EndingLineBreak {
|
|
public:
|
|
~EndingLineBreak() {
|
|
std::cout << std::endl;
|
|
};
|
|
};
|
|
}
|
|
|
|
/** @brief Operator to allow using SaveColor class with an ostream */
|
|
inline std::ostream& operator<<(std::ostream& os,
|
|
const microunit::SaveColor& obj) {
|
|
return os;
|
|
}
|
|
|
|
/** @brief Operator to allow using EndingLineBreak class with an ostream */
|
|
inline std::ostream& operator<<(std::ostream& os,
|
|
const microunit::EndingLineBreak& obj) {
|
|
return os;
|
|
}
|
|
|
|
/** @brief Operator to allow using Color class with an ostream */
|
|
inline std::ostream& operator<<(std::ostream& os,
|
|
const microunit::Color& color) {
|
|
color.Set();
|
|
return os;
|
|
}
|
|
|
|
/**
|
|
* @brief Macro for writing to the terminal an INFO-level log
|
|
*/
|
|
#define TERMINAL_INFO std::cout << microunit::SaveColor{} << \
|
|
microunit::EndingLineBreak{} << microunit::Yellow << "[ ] "
|
|
#define LOG_INFO TERMINAL_INFO << __FILENAME__ << ":" << __LINE__ << ": "
|
|
|
|
/**
|
|
* @brief Macro for writing to the terminal a BAD-level log
|
|
*/
|
|
#define TERMINAL_BAD std::cout << microunit::SaveColor{} << \
|
|
microunit::EndingLineBreak{} << microunit::Red << "[ ] "
|
|
#define LOG_BAD TERMINAL_BAD << __FILENAME__ << ":" << __LINE__ << ": "
|
|
|
|
/**
|
|
* @brief Macro for writing to the terminal a GOOD-level log
|
|
*/
|
|
#define TERMINAL_GOOD std::cout << microunit::SaveColor{} << \
|
|
microunit::EndingLineBreak{} << microunit::Green << "[ ] "
|
|
#define LOG_GOOD TERMINAL_GOOD << __FILENAME__ << ":" << __LINE__ << ": "
|
|
|
|
namespace microunit {
|
|
/**
|
|
* @brief Result of a unit test.
|
|
*/
|
|
struct UnitFunctionResult {
|
|
bool success{ true };
|
|
};
|
|
|
|
/**
|
|
* @brief Unit test function type.
|
|
*/
|
|
typedef void(*UnitFunction)(UnitFunctionResult*);
|
|
|
|
/**
|
|
* @brief Main class for unit test management. This class is a singleton
|
|
* and maintains a list of all registered unit test cases.
|
|
*/
|
|
class UnitTester {
|
|
public:
|
|
/**
|
|
* @brief Run all the registered unit test cases.
|
|
* @returns True if all tests pass, false otherwise.
|
|
*/
|
|
static bool Run() {
|
|
std::vector<std::string> failures;
|
|
|
|
// Iterate all registered unit tests
|
|
for (auto& unit : Instance().unitfunction_map_) {
|
|
std::cout << MICROUNIT_SEPARATOR << std::endl;
|
|
TERMINAL_GOOD << "Test case '" << unit.first << "'";
|
|
|
|
// Run the unit test
|
|
UnitFunctionResult result;
|
|
unit.second(&result);
|
|
|
|
if (!result.success) {
|
|
TERMINAL_BAD << "Failed test";
|
|
failures.push_back(unit.first);
|
|
} else {
|
|
TERMINAL_GOOD << "Passed test";
|
|
}
|
|
}
|
|
std::cout
|
|
<< MICROUNIT_SEPARATOR << std::endl
|
|
<< MICROUNIT_SEPARATOR << std::endl;
|
|
|
|
// Output result summary
|
|
if (failures.empty()) {
|
|
TERMINAL_GOOD << "All tests passed";
|
|
std::cout << MICROUNIT_SEPARATOR << std::endl;
|
|
return true;
|
|
} else {
|
|
TERMINAL_BAD << "Failed " << failures.size()
|
|
<< " test cases:";
|
|
for (const auto& failure : failures) {
|
|
TERMINAL_BAD << failure;
|
|
}
|
|
std::cout << MICROUNIT_SEPARATOR << std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Register a unit test case function. In regular library client usage,
|
|
* this doesn't need to be called, and the macro UNIT should be used
|
|
* instead.
|
|
* @param [in] name Name of the unit test case.
|
|
* @param [in] function Pointer to unit test case function.
|
|
* @returns True if all tests pass, false otherwise.
|
|
*/
|
|
static void RegisterFunction(const std::string &name,
|
|
UnitFunction function) {
|
|
Instance().unitfunction_map_.emplace(name, function);
|
|
}
|
|
|
|
/**
|
|
* @brief Helper class to register a unit test in construction time. This is
|
|
* used to call RegisterFunction in the construction of a static
|
|
* helper object. Used by the REGISTER_UNIT macro, which in turn is
|
|
* used by the UNIT macro.
|
|
* @returns True if all tests pass, false otherwise.
|
|
*/
|
|
class Registrator {
|
|
public:
|
|
Registrator(const std::string &name,
|
|
UnitFunction function) {
|
|
UnitTester::RegisterFunction(name, function);
|
|
};
|
|
Registrator(const Registrator&) = delete;
|
|
Registrator(Registrator&&) = delete;
|
|
~Registrator() {};
|
|
};
|
|
|
|
~UnitTester() {};
|
|
UnitTester(const UnitTester&) = delete;
|
|
UnitTester(UnitTester&&) = delete;
|
|
|
|
private:
|
|
UnitTester() {};
|
|
static UnitTester& Instance() {
|
|
static UnitTester instance;
|
|
return instance;
|
|
}
|
|
std::map<std::string, UnitFunction> unitfunction_map_;
|
|
};
|
|
}
|
|
|
|
#define MACROCAT_NEXP(A, B) A ## B
|
|
#define MACROCAT(A, B) MACROCAT_NEXP(A, B)
|
|
|
|
/**
|
|
* @brief Register a unit function using a helper static Registrator object.
|
|
*/
|
|
#define REGISTER_UNIT(FUNCTION) \
|
|
static microunit::UnitTester::Registrator \
|
|
MACROCAT(MICROUNIT_REGISTRATION, __COUNTER__)(#FUNCTION, FUNCTION);
|
|
|
|
/**
|
|
* @brief Define a unit function body. This macro is the one which should be used
|
|
* by client code to define unit test cases.
|
|
* @code{.cpp}
|
|
* UNIT(Test_Two_Plus_Two) {
|
|
* ASSERT_TRUE(2 + 2 == 4);
|
|
* };
|
|
* @endcode
|
|
*/
|
|
#define UNIT(FUNCTION) \
|
|
void FUNCTION(microunit::UnitFunctionResult*); \
|
|
REGISTER_UNIT(FUNCTION); \
|
|
void FUNCTION(microunit::UnitFunctionResult *__microunit_testresult)
|
|
|
|
/**
|
|
* @brief Pass the test and return from the test case.
|
|
*/
|
|
#define PASS() { \
|
|
LOG_GOOD << "Test stopped: Pass"; \
|
|
__microunit_testresult->success = true; \
|
|
return; \
|
|
}
|
|
|
|
/**
|
|
* @brief Fail the test and return from the test case.
|
|
*/
|
|
#define FAIL() { \
|
|
LOG_BAD << "Test stopped: Fail"; \
|
|
__microunit_testresult->success = false; \
|
|
return; \
|
|
}
|
|
|
|
/**
|
|
* @brief Check a particular test condition. If the condition does not hold,
|
|
* fail the test and return.
|
|
*/
|
|
#define ASSERT_TRUE(condition) if(!(condition)) { \
|
|
LOG_BAD << "Assert-true failed: " #condition; \
|
|
FAIL(); \
|
|
}
|
|
|
|
/**
|
|
* @brief Check a particular test condition. If the condition holds, fail the
|
|
* test and return.
|
|
*/
|
|
#define ASSERT_FALSE(condition) if((condition)) { \
|
|
LOG_BAD << "Assert-false failed: " #condition << std::endl; \
|
|
FAIL(); \
|
|
}
|
|
#endif
|