| @@ -44,23 +44,17 @@ protected: | |||||
| FixtureBase() : m_next(NULL), m_testcases(0), m_failcases(0) {} | FixtureBase() : m_next(NULL), m_testcases(0), m_failcases(0) {} | ||||
| virtual ~FixtureBase() {} | virtual ~FixtureBase() {} | ||||
| /* The FixtureBase class keeps track of all instanciated children | |||||
| * fixtures through this method. */ | |||||
| static FixtureBase *GetOrSetTest(FixtureBase *set = NULL) | |||||
| static void AddFixture(FixtureBase *fixture) | |||||
| { | { | ||||
| static FixtureBase *head = NULL, *tail = NULL; | |||||
| if (set) | |||||
| { | |||||
| if (!head) | |||||
| head = set; | |||||
| if (tail) | |||||
| tail->m_next = set; | |||||
| tail = set; | |||||
| } | |||||
| return head; | |||||
| /* Protect against several instances of the same Fixture subclass */ | |||||
| if (fixture->MarkFixture()) | |||||
| return; | |||||
| FixtureListHelper(fixture); | |||||
| } | } | ||||
| static FixtureBase *FixtureList() { return FixtureListHelper(NULL); } | |||||
| virtual void runFixture() = 0; | |||||
| virtual void RunFixture() = 0; | |||||
| virtual bool MarkFixture() = 0; | |||||
| /* Prevent compiler complaints about unreachable code */ | /* Prevent compiler complaints about unreachable code */ | ||||
| static inline bool True() { return true; } | static inline bool True() { return true; } | ||||
| @@ -70,23 +64,23 @@ protected: | |||||
| int m_asserts, m_failure; | int m_asserts, m_failure; | ||||
| char const *m_fixturename, *m_currentname; | char const *m_fixturename, *m_currentname; | ||||
| std::stringstream m_errorlog, m_context; | std::stringstream m_errorlog, m_context; | ||||
| }; | |||||
| #define LOLUNIT_FIXTURE(FixtureName) \ | |||||
| class FixtureName; \ | |||||
| template<typename T> struct Make##FixtureName \ | |||||
| { \ | |||||
| Make##FixtureName() { p = new T(); } \ | |||||
| ~Make##FixtureName() { delete p; } \ | |||||
| T *p; \ | |||||
| }; \ | |||||
| Make##FixtureName<FixtureName> lol_unit_fixture_##FixtureName; \ | |||||
| static char const *LolUnitFixtureName(FixtureName *p) \ | |||||
| { \ | |||||
| (void)p; \ | |||||
| return #FixtureName; \ | |||||
| } \ | |||||
| class FixtureName : public lol::Fixture<FixtureName> | |||||
| private: | |||||
| /* The FixtureBase class keeps track of all instanciated children | |||||
| * fixtures through this method. */ | |||||
| static FixtureBase *FixtureListHelper(FixtureBase *set) | |||||
| { | |||||
| static FixtureBase *head = NULL, *tail = NULL; | |||||
| if (set) | |||||
| { | |||||
| if (!head) head = set; | |||||
| if (tail) tail->m_next = set; | |||||
| tail = set; | |||||
| } | |||||
| return head; | |||||
| } | |||||
| }; | |||||
| /* | /* | ||||
| * This template specialises FixtureBase and provides registration of | * This template specialises FixtureBase and provides registration of | ||||
| @@ -97,35 +91,25 @@ template<class T> class Fixture : protected FixtureBase | |||||
| public: | public: | ||||
| typedef T FixtureClass; | typedef T FixtureClass; | ||||
| class TestCase | |||||
| struct TestCase | |||||
| { | { | ||||
| public: | |||||
| void (FixtureClass::* m_fun)(); | void (FixtureClass::* m_fun)(); | ||||
| char const *m_testname; | char const *m_testname; | ||||
| TestCase *m_next; | TestCase *m_next; | ||||
| static void AddTestCase(TestCase *that, char const *name, | |||||
| void (FixtureClass::*fun)()) | |||||
| { | |||||
| that->m_fun = fun; | |||||
| that->m_testname = name; | |||||
| that->m_next = NULL; | |||||
| GetOrSetTestCase(that); | |||||
| } | |||||
| }; | }; | ||||
| Fixture<T>() | Fixture<T>() | ||||
| { | { | ||||
| GetOrSetTest(this); | |||||
| AddFixture(this); | |||||
| } | } | ||||
| /* Run all test cases in this fixture. */ | /* Run all test cases in this fixture. */ | ||||
| virtual void runFixture() | |||||
| virtual void RunFixture() | |||||
| { | { | ||||
| m_errorlog.str(""); | m_errorlog.str(""); | ||||
| m_testcases = 0; | m_testcases = 0; | ||||
| m_failcases = 0; | m_failcases = 0; | ||||
| for (TestCase *c = GetOrSetTestCase(); c; c = c->m_next) | |||||
| for (TestCase *c = TestCaseList(); c; c = c->m_next) | |||||
| { | { | ||||
| m_testcases++; | m_testcases++; | ||||
| m_asserts = 0; | m_asserts = 0; | ||||
| @@ -139,9 +123,49 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| /* Mark the current fixture type as already registered and return whether | |||||
| * it was seen before. */ | |||||
| virtual bool MarkFixture() | |||||
| { | |||||
| static bool seen = false; | |||||
| if (seen) | |||||
| { | |||||
| SealFixture(); | |||||
| return true; | |||||
| } | |||||
| seen = true; | |||||
| return false; | |||||
| } | |||||
| /* Manage Fixture sealing. Once SealFixture() has been called, we | |||||
| * will no longer accept TestCase registrations. */ | |||||
| static void SealFixture() { SealFixtureHelper(true); } | |||||
| static bool IsFixtureSealed() { return SealFixtureHelper(false); } | |||||
| /* Each Fixture class specialisation keeps track of its instanciated | /* Each Fixture class specialisation keeps track of its instanciated | ||||
| * test cases through this method. */ | |||||
| static TestCase *GetOrSetTestCase(TestCase *set = NULL) | |||||
| * test cases. */ | |||||
| static void AddTestCase(TestCase *that, char const *name, | |||||
| void (FixtureClass::*fun)()) | |||||
| { | |||||
| if (IsFixtureSealed()) | |||||
| return; | |||||
| that->m_fun = fun; | |||||
| that->m_testname = name; | |||||
| that->m_next = NULL; | |||||
| TestCaseListHelper(that); | |||||
| } | |||||
| static TestCase *TestCaseList() { return TestCaseListHelper(NULL); } | |||||
| private: | |||||
| static bool SealFixtureHelper(bool set) | |||||
| { | |||||
| static bool sealed = false; | |||||
| if (set) sealed = true; | |||||
| return sealed; | |||||
| } | |||||
| static TestCase *TestCaseListHelper(TestCase *set) | |||||
| { | { | ||||
| static TestCase *head = NULL, *tail = NULL; | static TestCase *head = NULL, *tail = NULL; | ||||
| if (set) | if (set) | ||||
| @@ -154,19 +178,6 @@ public: | |||||
| } | } | ||||
| }; | }; | ||||
| #define LOLUNIT_TEST(TestCaseName) \ | |||||
| class TestCase##TestCaseName : public TestCase \ | |||||
| { \ | |||||
| public: \ | |||||
| TestCase##TestCaseName() \ | |||||
| { \ | |||||
| AddTestCase(this, #TestCaseName, \ | |||||
| (void (FixtureClass::*)()) &FixtureClass::TestCaseName); \ | |||||
| } \ | |||||
| }; \ | |||||
| TestCase##TestCaseName lol_unit_test_case_##TestCaseName; \ | |||||
| void TestCaseName() | |||||
| /* | /* | ||||
| * This simple class runs all automatically registered tests and reports | * This simple class runs all automatically registered tests and reports | ||||
| * on error and success in the standard output. | * on error and success in the standard output. | ||||
| @@ -180,10 +191,10 @@ public: | |||||
| std::stringstream errors(""); | std::stringstream errors(""); | ||||
| int failcases = 0, testcases = 0; | int failcases = 0, testcases = 0; | ||||
| for (FixtureBase *f = FixtureBase::GetOrSetTest(); f; f = f->m_next) | |||||
| for (FixtureBase *f = FixtureBase::FixtureList(); f; f = f->m_next) | |||||
| { | { | ||||
| f->setUp(); | f->setUp(); | ||||
| f->runFixture(); | |||||
| f->RunFixture(); | |||||
| f->tearDown(); | f->tearDown(); | ||||
| errors << f->m_errorlog.str(); | errors << f->m_errorlog.str(); | ||||
| @@ -221,7 +232,7 @@ public: | |||||
| { \ | { \ | ||||
| m_errorlog << std::endl << std::endl; \ | m_errorlog << std::endl << std::endl; \ | ||||
| m_errorlog << ++m_failcases << ") test: " \ | m_errorlog << ++m_failcases << ") test: " \ | ||||
| << LolUnitFixtureName(this) << "::" << m_currentname \ | |||||
| << lol_unit_helper_name(this) << "::" << m_currentname \ | |||||
| << " (F) line: " << __LINE__ << " " \ | << " (F) line: " << __LINE__ << " " \ | ||||
| << __FILE__ << std::endl; \ | << __FILE__ << std::endl; \ | ||||
| m_errorlog << "assertion failed" << std::endl; \ | m_errorlog << "assertion failed" << std::endl; \ | ||||
| @@ -230,7 +241,7 @@ public: | |||||
| m_failure = true; \ | m_failure = true; \ | ||||
| return; \ | return; \ | ||||
| } \ | } \ | ||||
| } while(!True()) | |||||
| } while (!True()) | |||||
| #define LOLUNIT_ASSERT_OP(op, modifier, opdesc, msg, a, b) \ | #define LOLUNIT_ASSERT_OP(op, modifier, opdesc, msg, a, b) \ | ||||
| do { \ | do { \ | ||||
| @@ -239,7 +250,7 @@ public: | |||||
| { \ | { \ | ||||
| m_errorlog << std::endl << std::endl; \ | m_errorlog << std::endl << std::endl; \ | ||||
| m_errorlog << ++m_failcases << ") test: " \ | m_errorlog << ++m_failcases << ") test: " \ | ||||
| << LolUnitFixtureName(this) << "::" << m_currentname \ | |||||
| << lol_unit_helper_name(this) << "::" << m_currentname \ | |||||
| << " (F) line: " << __LINE__ << " " \ | << " (F) line: " << __LINE__ << " " \ | ||||
| << __FILE__ << std::endl; \ | << __FILE__ << std::endl; \ | ||||
| m_errorlog << opdesc << " assertion failed" << std::endl; \ | m_errorlog << opdesc << " assertion failed" << std::endl; \ | ||||
| @@ -250,7 +261,7 @@ public: | |||||
| m_failure = true; \ | m_failure = true; \ | ||||
| return; \ | return; \ | ||||
| } \ | } \ | ||||
| } while(!True()) | |||||
| } while (!True()) | |||||
| #define LOLUNIT_MSG(msg) \ | #define LOLUNIT_MSG(msg) \ | ||||
| "- " << msg << std::endl | "- " << msg << std::endl | ||||
| @@ -262,7 +273,7 @@ public: | |||||
| { \ | { \ | ||||
| m_errorlog << std::endl << std::endl; \ | m_errorlog << std::endl << std::endl; \ | ||||
| m_errorlog << ++m_failcases << ") test: " \ | m_errorlog << ++m_failcases << ") test: " \ | ||||
| << LolUnitFixtureName(this) << "::" << m_currentname \ | |||||
| << lol_unit_helper_name(this) << "::" << m_currentname \ | |||||
| << " (F) line: " << __LINE__ << " " \ | << " (F) line: " << __LINE__ << " " \ | ||||
| << __FILE__ << std::endl; \ | << __FILE__ << std::endl; \ | ||||
| m_errorlog << "double equality assertion failed" << std::endl; \ | m_errorlog << "double equality assertion failed" << std::endl; \ | ||||
| @@ -277,7 +288,61 @@ public: | |||||
| m_failure = true; \ | m_failure = true; \ | ||||
| return; \ | return; \ | ||||
| } \ | } \ | ||||
| } while(!True()) | |||||
| } while (!True()) | |||||
| /* | |||||
| * Public helper macros | |||||
| */ | |||||
| #define LOLUNIT_FIXTURE(N) \ | |||||
| class N; \ | |||||
| /* This pattern allows us to statically create a Fixture instance \ | |||||
| * before its exact implementation was defined. */ \ | |||||
| template<typename T> struct lol_unit_helper_fixture_##N \ | |||||
| { \ | |||||
| lol_unit_helper_fixture_##N() { p = new T(); } \ | |||||
| ~lol_unit_helper_fixture_##N() { delete p; } \ | |||||
| T *p; \ | |||||
| }; \ | |||||
| lol_unit_helper_fixture_##N<N> lol_unit_helper_fixture_##N##_instance; \ | |||||
| /* Allow to retrieve the class name without using RTTI and without \ | |||||
| * knowing the type of "this". */ \ | |||||
| static inline char const *lol_unit_helper_name(N *p) \ | |||||
| { \ | |||||
| (void)p; \ | |||||
| return #N; \ | |||||
| } \ | |||||
| /* Now the user can define the implementation */ \ | |||||
| class N : public lol::Fixture<N> | |||||
| #define LOLUNIT_TEST(N) \ | |||||
| /* For each test in the fixture, we create an object that will \ | |||||
| * automatically register the test method in a list global to the \ | |||||
| * specialised fixture. */ \ | |||||
| class lol_unit_helper_test_##N : public TestCase \ | |||||
| { \ | |||||
| public: \ | |||||
| lol_unit_helper_test_##N() \ | |||||
| { \ | |||||
| AddTestCase(this, #N, \ | |||||
| (void (FixtureClass::*)()) &FixtureClass::N); \ | |||||
| } \ | |||||
| }; \ | |||||
| lol_unit_helper_test_##N lol_unit_helper_test_instance_##N; \ | |||||
| void N() | |||||
| /* | |||||
| * Provide context for error messages | |||||
| */ | |||||
| #define LOLUNIT_SET_CONTEXT(n) \ | |||||
| do { \ | |||||
| m_context.str(""); \ | |||||
| m_context << "- Context : " << #n << " = " << (n) << std::endl; \ | |||||
| } while (!True()) | |||||
| #define LOLUNIT_UNSET_CONTEXT(n) \ | |||||
| m_context.str("") | |||||
| /* | /* | ||||
| * Public assert macros | * Public assert macros | ||||
| @@ -288,7 +353,7 @@ public: | |||||
| m_asserts++; \ | m_asserts++; \ | ||||
| m_errorlog << std::endl << std::endl; \ | m_errorlog << std::endl << std::endl; \ | ||||
| m_errorlog << ++m_failcases << ") test: " \ | m_errorlog << ++m_failcases << ") test: " \ | ||||
| << LolUnitFixtureName(this) << "::" << m_currentname \ | |||||
| << lol_unit_helper_name(this) << "::" << m_currentname \ | |||||
| << " (F) line: " << __LINE__ << " " \ | << " (F) line: " << __LINE__ << " " \ | ||||
| << __FILE__ << std::endl; \ | << __FILE__ << std::endl; \ | ||||
| m_errorlog << "forced failure" << std::endl; \ | m_errorlog << "forced failure" << std::endl; \ | ||||
| @@ -296,16 +361,7 @@ public: | |||||
| m_errorlog << m_context.str(); \ | m_errorlog << m_context.str(); \ | ||||
| m_failure = true; \ | m_failure = true; \ | ||||
| return; \ | return; \ | ||||
| } while(!True()) | |||||
| #define LOLUNIT_SET_CONTEXT(n) \ | |||||
| do { \ | |||||
| m_context.str(""); \ | |||||
| m_context << "- Context : " << #n << " = " << (n) << std::endl; \ | |||||
| } while(!True()) | |||||
| #define LOLUNIT_UNSET_CONTEXT(n) \ | |||||
| m_context.str("") | |||||
| } while (!True()) | |||||
| #define LOLUNIT_ASSERT(cond) \ | #define LOLUNIT_ASSERT(cond) \ | ||||
| LOLUNIT_ASSERT_GENERIC("", cond) | LOLUNIT_ASSERT_GENERIC("", cond) | ||||