1 // ©2013-2014 Cameron Desrochers. 2 // Distributed under the simplified BSD license (see the LICENSE file that 3 // should have come with this header). 4 5 // Provides an extremely basic unit testing framework. 6 7 #pragma once 8 9 #include <cstdio> 10 #include <string> 11 #include <map> 12 #include <vector> 13 #include <type_traits> 14 #include <typeinfo> 15 16 #ifdef __GNUG__ 17 #include <cxxabi.h> 18 #include <cstdlib> 19 #endif 20 21 22 23 #define REGISTER_TEST(testName) registerTest(#testName, &subclass_t::testName) 24 25 #define ASSERT_OR_FAIL(expr) { if (!(expr)) { notifyTestFailed(__LINE__, #expr); return false; } } 26 #define SUCCEED() { return true; } 27 28 29 30 // Uses CRTP 31 template<typename TSubclass> 32 class TestClass 33 { 34 public: notifyTestFailed(int line,const char * expr)35 static void notifyTestFailed(int line, const char* expr) 36 { 37 std::printf(" FAILED!\n ******* Assertion failed (line %d): %s\n\n", line, expr); 38 } 39 validateTestName(std::string const & which)40 bool validateTestName(std::string const& which) const 41 { 42 return testMap.find(which) != testMap.end(); 43 } 44 getAllTestNames(std::vector<std::string> & names)45 void getAllTestNames(std::vector<std::string>& names) const 46 { 47 for (auto it = testMap.cbegin(); it != testMap.cend(); ++it) { 48 names.push_back(it->first); 49 } 50 } 51 52 bool run(unsigned int iterations = 1) 53 { 54 bool success = true; 55 for (auto it = testVec.cbegin(); it != testVec.cend(); ++it) { 56 if (!execTest(*it, iterations)) { 57 success = false; 58 } 59 } 60 return success; 61 } 62 63 bool run(std::vector<std::string> const& which, unsigned int iterations = 1) 64 { 65 bool success = true; 66 for (auto it = which.begin(); it != which.end(); ++it) { 67 if (!execTest(*testMap.find(*it), iterations)) { 68 success = false; 69 } 70 } 71 return success; 72 } 73 74 protected: 75 typedef TSubclass subclass_t; 76 registerTest(const char * name,bool (subclass_t::* method)())77 void registerTest(const char* name, bool (subclass_t::* method)()) 78 { 79 testVec.push_back(std::make_pair(std::string(name), method)); 80 testMap[std::string(name)] = method; 81 } 82 preTest()83 virtual bool preTest() { return true; } postTest(bool)84 virtual bool postTest(bool) { return true; } 85 execTest(std::pair<std::string,bool (subclass_t::*)()> const & testRef,unsigned int iterations)86 bool execTest(std::pair<std::string, bool (subclass_t::*)()> const& testRef, unsigned int iterations) 87 { 88 std::printf("%s::%s... \n", demangle_type_name(typeid(subclass_t).name()).c_str(), testRef.first.c_str()); 89 90 bool result = true; 91 for (unsigned int i = 0; result && i != iterations; ++i) { 92 result = preTest(); 93 try { 94 result = result && (static_cast<subclass_t*>(this)->*testRef.second)(); 95 } 96 catch (...) { 97 std::printf(" FAILED!\n ******* Unhandled exception thrown\n\n"); 98 result = false; 99 } 100 result = postTest(result) && result; 101 } 102 103 if (result) { 104 std::printf(" passed\n\n"); 105 } 106 return result; 107 } 108 109 private: demangle_type_name(const char * name)110 static std::string demangle_type_name(const char* name) 111 { 112 #ifdef __GNUG__ 113 // Adapted from http://stackoverflow.com/a/4541470/21475 114 int status = -4; 115 char* res = abi::__cxa_demangle(name, nullptr, nullptr, &status); 116 117 const char* const demangled_name = (status == 0) ? res : name; 118 std::string ret(demangled_name); 119 120 std::free(res); 121 return ret; 122 #else 123 return name; 124 #endif 125 } 126 127 protected: 128 std::vector<std::pair<std::string, bool (TSubclass::*)()> > testVec; 129 std::map<std::string, bool (TSubclass::*)()> testMap; 130 }; 131