source: trunk/src/lol/unit.h @ 928

Last change on this file since 928 was 928, checked in by sam, 11 years ago

lolunit: tweak output formatting so that it more closely matches CppUnit.

File size: 9.0 KB
Line 
1//
2// Lol Engine
3//
4// Copyright: (c) 2010-2011 Sam Hocevar <sam@hocevar.net>
5//   This program is free software; you can redistribute it and/or
6//   modify it under the terms of the Do What The Fuck You Want To
7//   Public License, Version 2, as published by Sam Hocevar. See
8//   http://sam.zoy.org/projects/COPYING.WTFPL for more details.
9//
10
11//
12// The Unit test framework
13// -----------------------
14//
15
16#if !defined __LOL_UNIT_H__
17#define __LOL_UNIT_H__
18
19#include <iostream>
20#include <iomanip>
21#include <sstream>
22#include <cstdio>
23#include <cmath>
24
25namespace lol
26{
27
28using namespace std;
29
30class FixtureBase
31{
32    friend class TextTestRunner;
33
34public:
35    virtual void setUp(void) {};
36    virtual void tearDown(void) {};
37
38protected:
39    FixtureBase() : m_next(NULL), m_testcases(0), m_failcases(0) {}
40    virtual ~FixtureBase() {}
41
42    /* The FixtureBase class keeps track of all instanciated children
43     * fixtures through this method. */
44    static FixtureBase *GetOrSetTest(FixtureBase *set = NULL)
45    {
46        static FixtureBase *head = NULL, *tail = NULL;
47        if (set)
48        {
49            if (!head)
50                head = set;
51            if (tail)
52                tail->m_next = set;
53            tail = set;
54        }
55        return head;
56    }
57
58    virtual void runFixture() = 0;
59
60    /* Prevent compiler complaints about unreachable code */
61    static inline bool True() { return true; }
62
63    FixtureBase *m_next;
64    int m_testcases, m_failcases;
65    int m_asserts, m_failure;
66    char const *m_fixturename, *m_currentname;
67    std::stringstream m_errorlog, m_context;
68};
69
70template<class T> class Fixture : protected FixtureBase
71{
72public:
73    typedef T FixtureClass;
74
75    class TestCase
76    {
77    public:
78        void (FixtureClass::* m_fun)();
79        char const *m_testname;
80        TestCase *m_next;
81
82        static void AddTestCase(TestCase *that, char const *name,
83                                void (FixtureClass::*fun)())
84        {
85            that->m_fun = fun;
86            that->m_testname = name;
87            that->m_next = NULL;
88            GetOrSetTestCase(that);
89        }
90    };
91
92    Fixture<T>()
93    {
94        GetOrSetTest(this);
95    }
96
97    /* Run all test cases in this fixture. */
98    virtual void runFixture()
99    {
100        m_errorlog.str("");
101        m_testcases = 0;
102        m_failcases = 0;
103        for (TestCase *c = GetOrSetTestCase(); c; c = c->m_next)
104        {
105            m_testcases++;
106            m_asserts = 0;
107            m_failure = false;
108            m_currentname = c->m_testname;
109            m_context.str("");
110
111            std::cout << '.';
112            (static_cast<FixtureClass *>(this)->*c->m_fun)();
113            if (m_failure) std::cout << 'F';
114        }
115    }
116
117    /* Each Fixture class specialisation keeps track of its instanciated
118     * test cases through this method. */
119    static TestCase *GetOrSetTestCase(TestCase *set = NULL)
120    {
121        static TestCase *head = NULL, *tail = NULL;
122        if (set)
123        {
124            if (!head) head = set;
125            if (tail) tail->m_next = set;
126            tail = set;
127        }
128        return head;
129    }
130};
131
132class TextTestRunner
133{
134public:
135    bool run()
136    {
137        bool ret = true;
138        std::stringstream errors("");
139        int failcases = 0, testcases = 0;
140
141        for (FixtureBase *f = FixtureBase::GetOrSetTest(); f; f = f->m_next)
142        {
143            f->setUp();
144            f->runFixture();
145            f->tearDown();
146
147            errors << f->m_errorlog.str();
148            testcases += f->m_testcases;
149            failcases += f->m_failcases;
150        }
151        std::cout << std::endl;
152
153        std::cout << std::endl << std::endl;
154        if (failcases)
155        {
156            std::cout << "!!!FAILURES!!!" << std::endl;
157            std::cout << "Test Results:" << std::endl;
158            std::cout << "Run:  " << testcases
159                      << "  Failures: " << failcases
160                      << "  Errors: 0" << std::endl; /* TODO: handle errors */
161
162            std::cout << errors.str();
163            ret = false;
164        }
165        else
166        {
167            std::cout << "OK (" << testcases << " tests)" << std::endl;
168        }
169        std::cout << std::endl << std::endl;
170
171        return ret;
172    }
173};
174
175#define LOLUNIT_FIXTURE(FixtureName) \
176    class FixtureName; \
177    template<typename T> struct Make##FixtureName \
178    { \
179        Make##FixtureName() { p = new T(); } \
180        ~Make##FixtureName() { delete p; } \
181        T *p; \
182    }; \
183    Make##FixtureName<FixtureName> lol_unit_fixture_##FixtureName; \
184    static char const *LolUnitFixtureName(FixtureName *p) \
185    { \
186        (void)p; \
187        return #FixtureName; \
188    } \
189    class FixtureName : public lol::Fixture<FixtureName>
190
191#define LOLUNIT_TEST(TestCaseName) \
192    class TestCase##TestCaseName : public TestCase \
193    { \
194    public: \
195        TestCase##TestCaseName() \
196        { \
197            AddTestCase(this, #TestCaseName, \
198                    (void (FixtureClass::*)()) &FixtureClass::TestCaseName); \
199        } \
200    }; \
201    TestCase##TestCaseName lol_unit_test_case_##TestCaseName; \
202    void TestCaseName()
203
204#define LOLUNIT_ASSERT_GENERIC(message, cond) \
205    do { \
206        m_asserts++; \
207        if (True() && !(cond)) \
208        { \
209            m_errorlog << std::endl << std::endl; \
210            m_errorlog << ++m_failcases << ") test: " \
211                       << LolUnitFixtureName(this) << "::" << m_currentname \
212                       << " (F) line: " << __LINE__ << " " \
213                       << __FILE__ << std::endl; \
214            m_errorlog << "assertion failed" << std::endl; \
215            m_errorlog << "- Expression: " << #cond << std::endl; \
216            m_errorlog << message; \
217            m_failure = true; \
218            return; \
219        } \
220    } while(!True())
221
222#define LOLUNIT_ASSERT_EQUAL_GENERIC(message, a, b) \
223    do { \
224        m_asserts++; \
225        if (True() && (a) != (b)) \
226        { \
227            m_errorlog << std::endl << std::endl; \
228            m_errorlog << ++m_failcases << ") test: " \
229                       << LolUnitFixtureName(this) << "::" << m_currentname \
230                       << " (F) line: " << __LINE__ << " " \
231                       << __FILE__ << std::endl; \
232            m_errorlog << "equality assertion failed" << std::endl; \
233            m_errorlog << "- Expected: " << #a << " = " << (a) << std::endl; \
234            m_errorlog << "- Actual  : " << #b << " = " << (b) << std::endl; \
235            m_errorlog << message; \
236            m_errorlog << m_context.str(); \
237            m_failure = true; \
238            return; \
239        } \
240    } while(!True())
241
242#define LOLUNIT_ASSERT_DOUBLES_EQUAL_GENERIC(message, a, b, t) \
243    do { \
244        m_asserts++; \
245        if (True() && fabs((a) - (b)) > fabs((t))) \
246        { \
247            m_errorlog << std::endl << std::endl; \
248            m_errorlog << ++m_failcases << ") test: " \
249                       << LolUnitFixtureName(this) << "::" << m_currentname \
250                       << " (F) line: " << __LINE__ << " " \
251                       << __FILE__ << std::endl; \
252            m_errorlog << "double equality assertion failed" << std::endl; \
253            std::streamsize old_prec = m_errorlog.precision(); \
254            m_errorlog << std::setprecision(16); \
255            m_errorlog << "- Expected: " << #a << " = " << (a) << std::endl; \
256            m_errorlog << "- Actual  : " << #b << " = " << (b) << std::endl; \
257            m_errorlog << "- Delta   : " << (t) << std::endl; \
258            m_errorlog << std::setprecision(old_prec); \
259            m_errorlog << message; \
260            m_errorlog << m_context.str(); \
261            m_failure = true; \
262            return; \
263        } \
264    } while(!True())
265
266#define LOLUNIT_FAIL(message) \
267    do { \
268        m_asserts++; \
269        m_errorlog << std::endl << std::endl; \
270        m_errorlog << ++m_failcases << ") test: " \
271                   << LolUnitFixtureName(this) << "::" << m_currentname \
272                   << " (F) line: " << __LINE__ << " " \
273                   << __FILE__ << std::endl; \
274        m_errorlog << "forced failure" << std::endl; \
275        m_errorlog << "- " << message << std::endl; \
276        m_errorlog << m_context.str(); \
277        m_failure = true; \
278        return; \
279    } while(!True())
280
281#define LOLUNIT_SET_CONTEXT(n) \
282    do { \
283        m_context.str(""); \
284        m_context << "- Context : " << #n << " = " << (n) << std::endl; \
285    } while(!True())
286
287#define LOLUNIT_UNSET_CONTEXT(n) \
288    m_context.str("")
289
290#define LOLUNIT_ASSERT(cond) \
291    LOLUNIT_ASSERT_GENERIC("", cond)
292
293#define LOLUNIT_ASSERT_MESSAGE(message, cond) \
294    LOLUNIT_ASSERT_GENERIC("- " << message << std::endl, cond)
295
296#define LOLUNIT_ASSERT_EQUAL(a, b) \
297    LOLUNIT_ASSERT_EQUAL_GENERIC("", a, b)
298
299#define LOLUNIT_ASSERT_EQUAL_MESSAGE(message, a, b) \
300    LOLUNIT_ASSERT_EQUAL_GENERIC("- " << message << std::endl, a, b)
301
302#define LOLUNIT_ASSERT_DOUBLES_EQUAL(a, b, t) \
303    LOLUNIT_ASSERT_DOUBLES_EQUAL_GENERIC("", a, b, t)
304
305#define LOLUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(message, a, b, t) \
306    LOLUNIT_ASSERT_DOUBLES_EQUAL_GENERIC("- " << message << std::endl, a, b, t)
307
308} /* namespace lol */
309
310#endif // __LOL_UNIT_H__
311
Note: See TracBrowser for help on using the repository browser.