From 2e314ddc2f1636a02c5a700a79948b6521d61ca5 Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Fri, 9 Sep 2011 14:39:00 +0000 Subject: [PATCH] lolunit: protect against multiple registration of the same fixture type. --- src/lol/unit.h | 212 +++++++++++++++++++++++++++++++------------------ 1 file changed, 134 insertions(+), 78 deletions(-) diff --git a/src/lol/unit.h b/src/lol/unit.h index 009510e3..63669042 100644 --- a/src/lol/unit.h +++ b/src/lol/unit.h @@ -44,23 +44,17 @@ protected: FixtureBase() : m_next(NULL), m_testcases(0), m_failcases(0) {} 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 */ static inline bool True() { return true; } @@ -70,23 +64,23 @@ protected: int m_asserts, m_failure; char const *m_fixturename, *m_currentname; std::stringstream m_errorlog, m_context; -}; -#define LOLUNIT_FIXTURE(FixtureName) \ - class FixtureName; \ - template struct Make##FixtureName \ - { \ - Make##FixtureName() { p = new T(); } \ - ~Make##FixtureName() { delete p; } \ - T *p; \ - }; \ - Make##FixtureName lol_unit_fixture_##FixtureName; \ - static char const *LolUnitFixtureName(FixtureName *p) \ - { \ - (void)p; \ - return #FixtureName; \ - } \ - class FixtureName : public lol::Fixture +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 @@ -97,35 +91,25 @@ template class Fixture : protected FixtureBase public: typedef T FixtureClass; - class TestCase + struct TestCase { - public: void (FixtureClass::* m_fun)(); char const *m_testname; 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() { - GetOrSetTest(this); + AddFixture(this); } /* Run all test cases in this fixture. */ - virtual void runFixture() + virtual void RunFixture() { m_errorlog.str(""); m_testcases = 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_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 - * 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; 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 * on error and success in the standard output. @@ -180,10 +191,10 @@ public: std::stringstream errors(""); 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->runFixture(); + f->RunFixture(); f->tearDown(); errors << f->m_errorlog.str(); @@ -221,7 +232,7 @@ public: { \ m_errorlog << std::endl << std::endl; \ m_errorlog << ++m_failcases << ") test: " \ - << LolUnitFixtureName(this) << "::" << m_currentname \ + << lol_unit_helper_name(this) << "::" << m_currentname \ << " (F) line: " << __LINE__ << " " \ << __FILE__ << std::endl; \ m_errorlog << "assertion failed" << std::endl; \ @@ -230,7 +241,7 @@ public: m_failure = true; \ return; \ } \ - } while(!True()) + } while (!True()) #define LOLUNIT_ASSERT_OP(op, modifier, opdesc, msg, a, b) \ do { \ @@ -239,7 +250,7 @@ public: { \ m_errorlog << std::endl << std::endl; \ m_errorlog << ++m_failcases << ") test: " \ - << LolUnitFixtureName(this) << "::" << m_currentname \ + << lol_unit_helper_name(this) << "::" << m_currentname \ << " (F) line: " << __LINE__ << " " \ << __FILE__ << std::endl; \ m_errorlog << opdesc << " assertion failed" << std::endl; \ @@ -250,7 +261,7 @@ public: m_failure = true; \ return; \ } \ - } while(!True()) + } while (!True()) #define LOLUNIT_MSG(msg) \ "- " << msg << std::endl @@ -262,7 +273,7 @@ public: { \ m_errorlog << std::endl << std::endl; \ m_errorlog << ++m_failcases << ") test: " \ - << LolUnitFixtureName(this) << "::" << m_currentname \ + << lol_unit_helper_name(this) << "::" << m_currentname \ << " (F) line: " << __LINE__ << " " \ << __FILE__ << std::endl; \ m_errorlog << "double equality assertion failed" << std::endl; \ @@ -277,7 +288,61 @@ public: m_failure = true; \ 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 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 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 + +#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 @@ -288,7 +353,7 @@ public: m_asserts++; \ m_errorlog << std::endl << std::endl; \ m_errorlog << ++m_failcases << ") test: " \ - << LolUnitFixtureName(this) << "::" << m_currentname \ + << lol_unit_helper_name(this) << "::" << m_currentname \ << " (F) line: " << __LINE__ << " " \ << __FILE__ << std::endl; \ m_errorlog << "forced failure" << std::endl; \ @@ -296,16 +361,7 @@ public: m_errorlog << m_context.str(); \ m_failure = true; \ 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) \ LOLUNIT_ASSERT_GENERIC("", cond)