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

Last change on this file since 932 was 932, checked in by sam, 9 years ago

lolunit: add some comments to document the framework internals.

File size: 11.8 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
30/*
31 * This is the base class for all fixtures. It keeps track of all
32 * fixtures registered through the LOLUNIT_FIXTURE macro and puts them
33 * in a linked list.
34 */
35class FixtureBase
36{
37    friend class TextTestRunner;
38
39public:
40    virtual void setUp(void) {};
41    virtual void tearDown(void) {};
42
43protected:
44    FixtureBase() : m_next(NULL), m_testcases(0), m_failcases(0) {}
45    virtual ~FixtureBase() {}
46
47    /* The FixtureBase class keeps track of all instanciated children
48     * fixtures through this method. */
49    static FixtureBase *GetOrSetTest(FixtureBase *set = NULL)
50    {
51        static FixtureBase *head = NULL, *tail = NULL;
52        if (set)
53        {
54            if (!head)
55                head = set;
56            if (tail)
57                tail->m_next = set;
58            tail = set;
59        }
60        return head;
61    }
62
63    virtual void runFixture() = 0;
64
65    /* Prevent compiler complaints about unreachable code */
66    static inline bool True() { return true; }
67
68    FixtureBase *m_next;
69    int m_testcases, m_failcases;
70    int m_asserts, m_failure;
71    char const *m_fixturename, *m_currentname;
72    std::stringstream m_errorlog, m_context;
73};
74
75#define LOLUNIT_FIXTURE(FixtureName) \
76    class FixtureName; \
77    template<typename T> struct Make##FixtureName \
78    { \
79        Make##FixtureName() { p = new T(); } \
80        ~Make##FixtureName() { delete p; } \
81        T *p; \
82    }; \
83    Make##FixtureName<FixtureName> lol_unit_fixture_##FixtureName; \
84    static char const *LolUnitFixtureName(FixtureName *p) \
85    { \
86        (void)p; \
87        return #FixtureName; \
88    } \
89    class FixtureName : public lol::Fixture<FixtureName>
90
91/*
92 * This template specialises FixtureBase and provides registration of
93 * test cases in a linked list through the LOLUNIT_TEST macro.
94 */
95template<class T> class Fixture : protected FixtureBase
96{
97public:
98    typedef T FixtureClass;
99
100    class TestCase
101    {
102    public:
103        void (FixtureClass::* m_fun)();
104        char const *m_testname;
105        TestCase *m_next;
106
107        static void AddTestCase(TestCase *that, char const *name,
108                                void (FixtureClass::*fun)())
109        {
110            that->m_fun = fun;
111            that->m_testname = name;
112            that->m_next = NULL;
113            GetOrSetTestCase(that);
114        }
115    };
116
117    Fixture<T>()
118    {
119        GetOrSetTest(this);
120    }
121
122    /* Run all test cases in this fixture. */
123    virtual void runFixture()
124    {
125        m_errorlog.str("");
126        m_testcases = 0;
127        m_failcases = 0;
128        for (TestCase *c = GetOrSetTestCase(); c; c = c->m_next)
129        {
130            m_testcases++;
131            m_asserts = 0;
132            m_failure = false;
133            m_currentname = c->m_testname;
134            m_context.str("");
135
136            std::cout << '.';
137            (static_cast<FixtureClass *>(this)->*c->m_fun)();
138            if (m_failure) std::cout << 'F';
139        }
140    }
141
142    /* Each Fixture class specialisation keeps track of its instanciated
143     * test cases through this method. */
144    static TestCase *GetOrSetTestCase(TestCase *set = NULL)
145    {
146        static TestCase *head = NULL, *tail = NULL;
147        if (set)
148        {
149            if (!head) head = set;
150            if (tail) tail->m_next = set;
151            tail = set;
152        }
153        return head;
154    }
155};
156
157#define LOLUNIT_TEST(TestCaseName) \
158    class TestCase##TestCaseName : public TestCase \
159    { \
160    public: \
161        TestCase##TestCaseName() \
162        { \
163            AddTestCase(this, #TestCaseName, \
164                    (void (FixtureClass::*)()) &FixtureClass::TestCaseName); \
165        } \
166    }; \
167    TestCase##TestCaseName lol_unit_test_case_##TestCaseName; \
168    void TestCaseName()
169
170/*
171 * This simple class runs all automatically registered tests and reports
172 * on error and success in the standard output.
173 */
174class TextTestRunner
175{
176public:
177    bool run()
178    {
179        bool ret = true;
180        std::stringstream errors("");
181        int failcases = 0, testcases = 0;
182
183        for (FixtureBase *f = FixtureBase::GetOrSetTest(); f; f = f->m_next)
184        {
185            f->setUp();
186            f->runFixture();
187            f->tearDown();
188
189            errors << f->m_errorlog.str();
190            testcases += f->m_testcases;
191            failcases += f->m_failcases;
192        }
193        std::cout << std::endl;
194
195        std::cout << std::endl << std::endl;
196        if (failcases)
197        {
198            std::cout << "!!!FAILURES!!!" << std::endl;
199            std::cout << "Test Results:" << std::endl;
200            std::cout << "Run:  " << testcases
201                      << "  Failures: " << failcases
202                      << "  Errors: 0" << std::endl; /* TODO: handle errors */
203
204            std::cout << errors.str();
205            ret = false;
206        }
207        else
208        {
209            std::cout << "OK (" << testcases << " tests)" << std::endl;
210        }
211        std::cout << std::endl << std::endl;
212
213        return ret;
214    }
215};
216
217#define LOLUNIT_ASSERT_GENERIC(msg, cond) \
218    do { \
219        m_asserts++; \
220        if (True() && !(cond)) \
221        { \
222            m_errorlog << std::endl << std::endl; \
223            m_errorlog << ++m_failcases << ") test: " \
224                       << LolUnitFixtureName(this) << "::" << m_currentname \
225                       << " (F) line: " << __LINE__ << " " \
226                       << __FILE__ << std::endl; \
227            m_errorlog << "assertion failed" << std::endl; \
228            m_errorlog << "- Expression: " << #cond << std::endl; \
229            m_errorlog << msg; \
230            m_failure = true; \
231            return; \
232        } \
233    } while(!True())
234
235#define LOLUNIT_ASSERT_OP(op, modifier, opdesc, msg, a, b) \
236    do { \
237        m_asserts++; \
238        if (True() && !modifier((a) op (b))) \
239        { \
240            m_errorlog << std::endl << std::endl; \
241            m_errorlog << ++m_failcases << ") test: " \
242                       << LolUnitFixtureName(this) << "::" << m_currentname \
243                       << " (F) line: " << __LINE__ << " " \
244                       << __FILE__ << std::endl; \
245            m_errorlog << opdesc << " assertion failed" << std::endl; \
246            m_errorlog << "- Expected: " << #a << " = " << (a) << std::endl; \
247            m_errorlog << "- Actual  : " << #b << " = " << (b) << std::endl; \
248            m_errorlog << msg; \
249            m_errorlog << m_context.str(); \
250            m_failure = true; \
251            return; \
252        } \
253    } while(!True())
254
255#define LOLUNIT_MSG(msg) \
256    "- " << msg << std::endl
257
258#define LOLUNIT_ASSERT_DOUBLES_EQUAL_GENERIC(msg, a, b, t) \
259    do { \
260        m_asserts++; \
261        if (True() && fabs((a) - (b)) > fabs((t))) \
262        { \
263            m_errorlog << std::endl << std::endl; \
264            m_errorlog << ++m_failcases << ") test: " \
265                       << LolUnitFixtureName(this) << "::" << m_currentname \
266                       << " (F) line: " << __LINE__ << " " \
267                       << __FILE__ << std::endl; \
268            m_errorlog << "double equality assertion failed" << std::endl; \
269            std::streamsize old_prec = m_errorlog.precision(); \
270            m_errorlog << std::setprecision(16); \
271            m_errorlog << "- Expected: " << #a << " = " << (a) << std::endl; \
272            m_errorlog << "- Actual  : " << #b << " = " << (b) << std::endl; \
273            m_errorlog << "- Delta   : " << (t) << std::endl; \
274            m_errorlog << std::setprecision(old_prec); \
275            m_errorlog << msg; \
276            m_errorlog << m_context.str(); \
277            m_failure = true; \
278            return; \
279        } \
280    } while(!True())
281
282/*
283 * Public assert macros
284 */
285
286#define LOLUNIT_FAIL(msg) \
287    do { \
288        m_asserts++; \
289        m_errorlog << std::endl << std::endl; \
290        m_errorlog << ++m_failcases << ") test: " \
291                   << LolUnitFixtureName(this) << "::" << m_currentname \
292                   << " (F) line: " << __LINE__ << " " \
293                   << __FILE__ << std::endl; \
294        m_errorlog << "forced failure" << std::endl; \
295        m_errorlog << LOLUNIT_MSG(msg); \
296        m_errorlog << m_context.str(); \
297        m_failure = true; \
298        return; \
299    } while(!True())
300
301#define LOLUNIT_SET_CONTEXT(n) \
302    do { \
303        m_context.str(""); \
304        m_context << "- Context : " << #n << " = " << (n) << std::endl; \
305    } while(!True())
306
307#define LOLUNIT_UNSET_CONTEXT(n) \
308    m_context.str("")
309
310#define LOLUNIT_ASSERT(cond) \
311    LOLUNIT_ASSERT_GENERIC("", cond)
312#define LOLUNIT_ASSERT_MESSAGE(m, cond) \
313    LOLUNIT_ASSERT_GENERIC(LOLUNIT_MSG(m), cond)
314
315
316#define LOLUNIT_ASSERT_EQUAL(a, b) \
317    LOLUNIT_ASSERT_OP(==, !!, "equality", "", a, b)
318#define LOLUNIT_ASSERT_EQUAL_MESSAGE(m, a, b) \
319    LOLUNIT_ASSERT_OP(==, !!, "equality", LOLUNIT_MSG(m), a, b)
320#define LOLUNIT_ASSERT_DIFFERENT(a, b) \
321    LOLUNIT_ASSERT_OP(!=, !!, "inequality", "", a, b)
322#define LOLUNIT_ASSERT_DIFFERENT_MESSAGE(m, a, b) \
323    LOLUNIT_ASSERT_OP(!=, !!, "inequality", LOLUNIT_MSG(m), a, b)
324#define LOLUNIT_ASSERT_LESS(a, b) \
325    LOLUNIT_ASSERT_OP(<, !!, "less than", "", a, b)
326#define LOLUNIT_ASSERT_LESS_MESSAGE(m, a, b) \
327    LOLUNIT_ASSERT_OP(<, !!, "less than", LOLUNIT_MSG(m), a, b)
328#define LOLUNIT_ASSERT_LEQUAL(a, b) \
329    LOLUNIT_ASSERT_OP(<=, !!, "less than or equal", "", a, b)
330#define LOLUNIT_ASSERT_LEQUAL_MESSAGE(m, a, b) \
331    LOLUNIT_ASSERT_OP(<=, !!, "less than or equal", LOLUNIT_MSG(m), a, b)
332#define LOLUNIT_ASSERT_GREATER(a, b) \
333    LOLUNIT_ASSERT_OP(>, !!, "greater than", "", a, b)
334#define LOLUNIT_ASSERT_GREATER_MESSAGE(m, a, b) \
335    LOLUNIT_ASSERT_OP(>, !!, "greater than", LOLUNIT_MSG(m), a, b)
336#define LOLUNIT_ASSERT_GEQUAL(a, b) \
337    LOLUNIT_ASSERT_OP(>=, !!, "greater than or equal", "", a, b)
338#define LOLUNIT_ASSERT_GEQUAL_MESSAGE(m, a, b) \
339    LOLUNIT_ASSERT_OP(>=, !!, "greater than or equal", LOLUNIT_MSG(m), a, b)
340
341
342#define LOLUNIT_ASSERT_NOT_EQUAL(a, b) \
343    LOLUNIT_ASSERT_OP(==, !, "not equality", "", a, b)
344#define LOLUNIT_ASSERT_NOT_EQUAL_MESSAGE(m, a, b) \
345    LOLUNIT_ASSERT_OP(==, !, "not equality", LOLUNIT_MSG(m), a, b)
346#define LOLUNIT_ASSERT_NOT_DIFFERENT(a, b) \
347    LOLUNIT_ASSERT_OP(!=, !, "not inequality", "", a, b)
348#define LOLUNIT_ASSERT_NOT_DIFFERENT_MESSAGE(m, a, b) \
349    LOLUNIT_ASSERT_OP(!=, !, "not inequality", LOLUNIT_MSG(m), a, b)
350#define LOLUNIT_ASSERT_NOT_LESS(a, b) \
351    LOLUNIT_ASSERT_OP(<, !, "not less than", "", a, b)
352#define LOLUNIT_ASSERT_NOT_LESS_MESSAGE(m, a, b) \
353    LOLUNIT_ASSERT_OP(<, !, "not less than", LOLUNIT_MSG(m), a, b)
354#define LOLUNIT_ASSERT_NOT_LEQUAL(a, b) \
355    LOLUNIT_ASSERT_OP(<=, !, "not less than or equal", "", a, b)
356#define LOLUNIT_ASSERT_NOT_LEQUAL_MESSAGE(m, a, b) \
357    LOLUNIT_ASSERT_OP(<=, !, "not less than or equal", LOLUNIT_MSG(m), a, b)
358#define LOLUNIT_ASSERT_NOT_GREATER(a, b) \
359    LOLUNIT_ASSERT_OP(>, !, "not greater than", "", a, b)
360#define LOLUNIT_ASSERT_NOT_GREATER_MESSAGE(m, a, b) \
361    LOLUNIT_ASSERT_OP(>, !, "not greater than", LOLUNIT_MSG(m), a, b)
362#define LOLUNIT_ASSERT_NOT_GEQUAL(a, b) \
363    LOLUNIT_ASSERT_OP(>=, !, "not greater than or equal", "", a, b)
364#define LOLUNIT_ASSERT_NOT_GEQUAL_MESSAGE(m, a, b) \
365    LOLUNIT_ASSERT_OP(>=, !, "not greater than or equal", LOLUNIT_MSG(m), a, b)
366
367#define LOLUNIT_ASSERT_DOUBLES_EQUAL(a, b, t) \
368    LOLUNIT_ASSERT_DOUBLES_EQUAL_GENERIC("", a, b, t)
369#define LOLUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(msg, a, b, t) \
370    LOLUNIT_ASSERT_DOUBLES_EQUAL_GENERIC(LOLUNIT_MSG(msg), a, b, t)
371
372} /* namespace lol */
373
374#endif // __LOL_UNIT_H__
375
Note: See TracBrowser for help on using the repository browser.