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