1 // This file is part of VSTGUI. It is subject to the license terms
2 // in the LICENSE file found in the top-level directory of this
3 // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
4 
5 #pragma once
6 
7 #if ENABLE_UNIT_TESTS
8 
9 #include "../../lib/vstguifwd.h"
10 #include <string>
11 #include <list>
12 #include <functional>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <stdexcept>
16 
17 /*
18 	How-to write tests:
19 
20 	1) include this file
21 	2) optional: put the test code into namespaces: namespace VSTGUI { namespace UnitTest {
22 	3) open testcase : TESTCASE (MySuite,
23 	4) optional : declare SETUP and TEARDOWN functions
24 	5) declare tests : TEST (MyTest, testing code);
25 	6) inside of "testing code" use EXPECT or FAIL macros
26 	7) close testcase: );
27 	8) optional: close namespaces: }}
28 
29 	Complete Example:
30 
31 		#include "unittests.h"
32 
33 		TESTCASE(Example,
34 
35 			int result;
36 
37 			SETUP(
38 				result = 0;
39 			);
40 
41 			TEST(OnePlusOneIsTwo,
42 				result = 1+1;
43 				EXPECT (result == 2)
44 			);
45 
46 			TEST(ThreeMinusOneIsTwo,
47 				result = 3-1;
48 				if (result != 2)
49 				{
50 					FAIL ("result is not two")
51 				}
52 			);
53 		);
54 
55 */
56 
57 namespace VSTGUI {
58 namespace UnitTest {
59 
60 //----------------------------------------------------------------------------------------------------
61 class error : public std::logic_error
62 {
63 public:
error(const char * str)64 	error (const char* str) : std::logic_error (str) {}
65 };
66 
67 #define	VSTGUI_UNITTEST_MAKE_STRING_PRIVATE_DONT_USE(x)	# x
68 #define	VSTGUI_UNITTEST_MAKE_STRING(x) VSTGUI_UNITTEST_MAKE_STRING_PRIVATE_DONT_USE(x)
69 
70 //----------------------------------------------------------------------------------------------------
71 #define TESTCASE(name,function) static VSTGUI::UnitTest::TestCaseRegistrar name##TestCaseRegistrar (VSTGUI_UNITTEST_MAKE_STRING(name), [](VSTGUI::UnitTest::TestCase* testCase) { function })
72 #define TEST(name,function) testCase->registerTest (VSTGUI_UNITTEST_MAKE_STRING(name), [](VSTGUI::UnitTest::Context* context) { { function } return true; });
73 #define EXPECT(condition) if (!(condition)) { throw VSTGUI::UnitTest::error (__FILE__ ":" VSTGUI_UNITTEST_MAKE_STRING(__LINE__) ": Expected: " VSTGUI_UNITTEST_MAKE_STRING(condition)); }
74 #define FAIL(reason) { context->print (__FILE__ ":" VSTGUI_UNITTEST_MAKE_STRING(__LINE__) ": Failure: " reason); return false; }
75 
76 #define EXPECT_EXCEPTION(call, name) \
77 { \
78 	bool b = false; \
79 	try { \
80 		call; \
81 	} catch (const std::exception& error) {\
82 		EXPECT(error.what() == std::string(name));\
83 		b = true;\
84 	} \
85 	EXPECT(b);\
86 }
87 
88 #define SETUP(function) testCase->setSetupFunction ([](VSTGUI::UnitTest::Context* context) { function } )
89 #define TEARDOWN(function) testCase->setTeardownFunction ([](VSTGUI::UnitTest::Context* context) { function } )
90 //----------------------------------------------------------------------------------------------------
91 class Context;
92 class TestCase;
93 
94 //----------------------------------------------------------------------------------------------------
95 using TestFunction = std::function<bool(Context*)>;
96 using SetupFunction = std::function<void(Context*)>;
97 using TeardownFunction = std::function<void(Context*)>;
98 using TestCaseFunction = std::function<void(TestCase*)>;
99 
100 //----------------------------------------------------------------------------------------------------
101 class UnitTestRegistry
102 {
103 	using TestCases = std::list<TestCase>;
104 	using Iterator = TestCases::const_iterator;
105 public:
106 	static UnitTestRegistry& instance ();
107 
108 	void registerTestCase (TestCase&& testCase);
109 
begin()110 	Iterator begin () const { return testCases.begin (); }
end()111 	Iterator end () const { return testCases.end (); }
112 private:
113 	TestCases testCases;
114 };
115 
116 //----------------------------------------------------------------------------------------------------
117 class TestCase
118 {
119 	using TestPair = std::pair<std::string, TestFunction>;
120 	using Tests = std::list<TestPair>;
121 	using Iterator = Tests::const_iterator;
122 public:
123 	TestCase (std::string&& name, TestCaseFunction&& testCase);
124 	TestCase (TestCase&& tc) noexcept;
125 
126 	void setSetupFunction (SetupFunction&& setupFunction);
127 	void setTeardownFunction (TeardownFunction&& teardownFunction);
128 	void registerTest (std::string&& name, TestFunction&& function);
129 
getName()130 	const std::string& getName () const { return name; }
131 
begin()132 	Iterator begin () const { return tests.begin (); }
end()133 	Iterator end () const { return tests.end (); }
134 
setup()135 	const SetupFunction& setup () const { return setupFunction; }
teardown()136 	const TeardownFunction& teardown () const { return teardownFunction; }
137 
138 	TestCase& operator= (TestCase&& tc) noexcept;
139 private:
140 	Tests tests;
141 	std::string name;
142 	TestCaseFunction tcf;
143 	SetupFunction setupFunction;
144 	TeardownFunction teardownFunction;
145 };
146 
147 //----------------------------------------------------------------------------------------------------
148 class TestCaseRegistrar
149 {
150 public:
TestCaseRegistrar(std::string && name,TestCaseFunction && testCase)151 	TestCaseRegistrar (std::string&& name, TestCaseFunction&& testCase)
152 	{
153 		UnitTestRegistry::instance().registerTestCase (TestCase (std::move (name), std::move (testCase)));
154 	}
155 };
156 
157 //----------------------------------------------------------------------------------------------------
158 class Context
159 {
160 public:
161 	void print (const char* fmt, ...);
162 	virtual void printRaw (const char* str) = 0;
163 };
164 
165 }} // namespaces
166 
167 #else
168 
169 #define TESTCASE(x,y)
170 
171 #endif
172