/** test.h, an extremly simple test framework. * Version 1.4 * Copyright (C) 2022-2023 Tobias Kreilos, Offenburg University of Applied * Sciences * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * * * The framework defines a function check(a,b) that can be called with * parameters of different types. The function asserts * that the two paramters are equal (within a certain, predefined range for * floating point numbers) and prints the result of the comparison on the * command line. Additionally a summary of all tests is printed at the end of * the program. * Additionally there is TEST macro, which you can place outside main to group * tests together. Code in the macro is automatically executed at the beginning * of the program. * The file also defines a class InstanceCount, that can be used to * count how many instances of an object are still alive at the end of a * program. To use it, derive your class from InstanceCount and the * message is automatically printed at the end of the program. */ #ifndef VERY_SIMPLE_TEST_H #define VERY_SIMPLE_TEST_H #include #include #include #include /** Simple macro to execute the code that follows the macro (without call from * main) * * Define a class, that is directly instantiated * and contains the test code in the constructor. * * Usage: * TEST(MyTest) * { * // test code * } */ #define TEST(name) \ struct _TestClass##name { \ _TestClass##name(); \ } _TestClass##name##Instance; \ _TestClass##name::_TestClass##name() // Use a namespace to hide implementation details namespace Test::Detail { /** * Make it possible to print the underlying value of class enums with ostream * * The expression typename std::enable_if::value, * std::ostream>::type decays to ostream if the type T is an enum. Otherwise, * the function is not generated. */ template std::ostream& operator<<( typename std::enable_if::value, std::ostream>::type& stream, const T& e) { return stream << static_cast::type>(e); } /** * Convert anything to a string. */ template std::string toString(const T& t) { std::ostringstream ss; ss << t; return "\"" + ss.str() + "\""; } /** * Convert bools to string "true" or "false" instead of 0 and 1 */ template <> inline std::string toString(const bool& b) { return b ? "\"true\"" : "\"false\""; } /** * Comparison function for different types */ template bool isEqual(const T& t1, const T& t2) { return t1 == t2; } /** * Double values are equal if they differ no more than 1e-12 */ template <> inline bool isEqual(const double& expectedValue, const double& actualValue) { const double epsilon = 1e-12; const double distance = fabs(actualValue - expectedValue); return (distance < epsilon); } /** * Float values are equal if they differ no more than 1e-6 */ template <> inline bool isEqual(const float& expectedValue, const float& actualValue) { const double epsilon = 1e-6; const double distance = fabs(actualValue - expectedValue); return (distance < epsilon); } /** * This class realizes some basics of the test framework. * Test summary is printed in the destructor. * Apart from that, the class implements counting of total and failed tests, * comparison of floating point numbers within sensible boundaries and prints * the result of each test on the command line. */ class Test { public: /** * Test class is a Singleton */ static Test& instance() { static Test test; return test; } /** * the main entry point for tests. Test two values for equality and output the * result. */ template bool check(const T& expectedValue, const T& actualValue) { bool testResult = isEqual(expectedValue, actualValue); if (testResult == true) { registerPassingTest(); #pragma omp critical std::cout << "Test successful! Expected value == actual value (=" << toString(expectedValue) << ")" << std::endl; } else { registerFailingTest(); #pragma omp critical std::cout << "Error in test: expected value " << toString(expectedValue) << ", but actual value was " << toString(actualValue) << std::endl; } return testResult; } private: /** * On destruction, print a summary of all tests. */ ~Test() { std::cout << "\n--------------------------------------" << std::endl; std::cout << "Test summary:" << std::endl; std::cout << "Executed tests: " << numTests_ << std::endl; std::cout << "Failed tests: " << numFailedTests_ << std::endl; } void registerPassingTest() { numTests_++; } void registerFailingTest() { numTests_++; numFailedTests_++; } /** * For statistics */ std::atomic numTests_ = 0; /** * For statistics */ std::atomic numFailedTests_ = 0; }; template class InstanceCounterHelper { public: ~InstanceCounterHelper() { std::cout << "The remaining number of objects of type " << typeid(T).name() << " at the end of the program is " << count; if (count > 0) std::cout << " (NOT zero!)"; std::cout << "\nThe total number of objects created was " << total << std::endl; } void increment() { count++; total++; } void decrement() { count--; } private: std::atomic count = 0; std::atomic total = 0; }; } // namespace Test::Detail /** * Count the instances of a class T. * Result gets printed automatically at the end of the program. * To use it, inherit T from InstanceCounter, e.g. * class MyClass : InstanceCounter */ template class InstanceCounter { public: InstanceCounter() { counter().increment(); } InstanceCounter(const InstanceCounter&) { counter().increment(); } InstanceCounter(const InstanceCounter&&) { counter().increment(); } virtual ~InstanceCounter() { counter().decrement(); } Test::Detail::InstanceCounterHelper& counter() { static Test::Detail::InstanceCounterHelper c; return c; } }; /** * Check if the expected value is equal to the actual value. * Result is printed on the command line and at the end of the program, a * summary of all tests is printed. */ template void check(const T1& actualValue, const T2& expectedValue) { const T1& expectedValueCasted{ expectedValue}; // allows conversion in general, but avoids narrowing // conversion Test::Detail::Test::instance().check(expectedValueCasted, actualValue); } // allow conversion from int to double explicitely template <> inline void check(const int& actualValue, const double& expectedValue) { Test::Detail::Test::instance().check(expectedValue, static_cast(actualValue)); } // allow conversion from int to double explicitely template <> inline void check(const double& actualValue, const int& expectedValue) { Test::Detail::Test::instance().check(static_cast(expectedValue), actualValue); } /** * Check if the entered value is true. * Result is printed on the command line and at the end of the program, a * summary of all tests is printed. */ inline void check(bool a) { Test::Detail::Test::instance().check(true, a); } #endif // VERY_SIMPLE_TEST_H /** * V1.0: Creation of franework * V1.1: make check(bool) inline, automatically convert expected value type to * actual value type * V1.2: added possibilty to count constructions and destructions of some type * V1.3: tweaks on check for int and double types * V1.4: Adding thread safety in OpenMP programs (not general thread safety, as * OpenMP and std::thread might not play along) */