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