]> git.stg.codes - stg.git/blob - tests/tut/tut.hpp
Lang attribute added to the book tag
[stg.git] / tests / tut / tut.hpp
1 #ifndef TUT_H_GUARD
2 #define TUT_H_GUARD
3
4 #include <iostream>
5 #include <map>
6 #include <vector>
7 #include <set>
8 #include <string>
9 #include <sstream>
10 #include <iterator>
11 #include <algorithm>
12 #include <typeinfo>
13
14 #if defined(linux)
15 #define TUT_USE_POSIX
16 #endif
17
18 #include "tut_exception.hpp"
19 #include "tut_result.hpp"
20 #include "tut_posix.hpp"
21 #include "tut_assert.hpp"
22 #include "tut_runner.hpp"
23
24 #if defined(TUT_USE_SEH)
25 #include <windows.h>
26 #include <winbase.h>
27 #endif
28
29 /**
30  * Template Unit Tests Framework for C++.
31  * http://tut.dozen.ru
32  *
33  * @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
34  */
35 namespace tut
36 {
37
38 /**
39  * Test object. Contains data test run upon and default test method
40  * implementation. Inherited from Data to allow tests to
41  * access test data as members.
42  */
43 template <class Data>
44 class test_object : public Data, public test_object_posix
45 {
46 public:
47
48     /**
49      * Default constructor
50      */
51     test_object()
52     {
53     }
54
55     void set_test_name(const std::string& current_test_name)
56     {
57         current_test_name_ = current_test_name;
58     }
59
60     const std::string& get_test_name() const
61     {
62         return current_test_name_;
63     }
64
65     void set_test_id(int current_test_id)
66     {
67         current_test_id_ = current_test_id;
68     }
69
70     int get_test_id() const
71     {
72         return current_test_id_;
73     }
74
75     /**
76      * Default do-nothing test.
77      */
78     template <int n>
79     void test()
80     {
81         called_method_was_a_dummy_test_ = true;
82     }
83
84     /**
85      * The flag is set to true by default (dummy) test.
86      * Used to detect usused test numbers and avoid unnecessary
87      * test object creation which may be time-consuming depending
88      * on operations described in Data::Data() and Data::~Data().
89      * TODO: replace with throwing special exception from default test.
90      */
91     bool called_method_was_a_dummy_test_;
92
93 private:
94     int             current_test_id_;
95     std::string     current_test_name_;
96 };
97
98
99 /**
100  * Walks through test tree and stores address of each
101  * test method in group. Instantiation stops at 0.
102  */
103 template <class Test, class Group, int n>
104 struct tests_registerer
105 {
106     static void reg(Group& group)
107     {
108         group.reg(n, &Test::template test<n>);
109         tests_registerer<Test, Group, n - 1>::reg(group);
110     }
111 };
112
113 template <class Test, class Group>
114 struct tests_registerer<Test, Group, 0>
115 {
116     static void reg(Group&)
117     {
118     }
119 };
120
121 /**
122  * Test group; used to recreate test object instance for
123  * each new test since we have to have reinitialized
124  * Data base class.
125  */
126 template <class Data, int MaxTestsInGroup = 50>
127 class test_group : public group_base, public test_group_posix
128 {
129     const char* name_;
130
131     typedef void (test_object<Data>::*testmethod)();
132     typedef std::map<int, testmethod> tests;
133     typedef typename tests::iterator tests_iterator;
134     typedef typename tests::const_iterator tests_const_iterator;
135     typedef typename tests::const_reverse_iterator
136     tests_const_reverse_iterator;
137     typedef typename tests::size_type size_type;
138
139     tests tests_;
140     tests_iterator current_test_;
141
142         enum seh_result
143         {
144                 SEH_OK,
145                 SEH_CTOR,
146                 SEH_TEST,
147                 SEH_DUMMY
148         };
149
150     /**
151      * Exception-in-destructor-safe smart-pointer class.
152      */
153     template <class T>
154     class safe_holder
155     {
156         T* p_;
157         bool permit_throw_in_dtor;
158
159         safe_holder(const safe_holder&);
160         safe_holder& operator=(const safe_holder&);
161
162     public:
163         safe_holder()
164             : p_(0),
165               permit_throw_in_dtor(false)
166         {
167         }
168
169         ~safe_holder()
170         {
171             release();
172         }
173
174         T* operator->() const
175         {
176             return p_;
177         }
178
179         T* get() const
180         {
181             return p_;
182         }
183
184         /**
185          * Tell ptr it can throw from destructor. Right way is to
186          * use std::uncaught_exception(), but some compilers lack
187          * correct implementation of the function.
188          */
189         void permit_throw()
190         {
191             permit_throw_in_dtor = true;
192         }
193
194         /**
195          * Specially treats exceptions in test object destructor;
196          * if test itself failed, exceptions in destructor
197          * are ignored; if test was successful and destructor failed,
198          * warning exception throwed.
199          */
200         void release()
201         {
202             try
203             {
204                 if (delete_obj() == false)
205                 {
206                     throw warning("destructor of test object raised"
207                         " an SEH exception");
208                 }
209             }
210             catch (const std::exception& ex)
211             {
212                 if (permit_throw_in_dtor)
213                 {
214                     std::string msg = "destructor of test object raised"
215                         " exception: ";
216                     msg += ex.what();
217                     throw warning(msg);
218                 }
219             }
220             catch( ... )
221             {
222                 if (permit_throw_in_dtor)
223                 {
224                     throw warning("destructor of test object raised an"
225                         " exception");
226                 }
227             }
228         }
229
230         /**
231          * Re-init holder to get brand new object.
232          */
233         void reset()
234         {
235             release();
236             permit_throw_in_dtor = false;
237             p_ = new T();
238         }
239
240         bool delete_obj()
241         {
242 #if defined(TUT_USE_SEH)
243             __try
244             {
245 #endif
246                 T* p = p_;
247                 p_ = 0;
248                 delete p;
249 #if defined(TUT_USE_SEH)
250             }
251             __except(handle_seh_(::GetExceptionCode()))
252             {
253                 if (permit_throw_in_dtor)
254                 {
255                     return false;
256                 }
257             }
258 #endif
259             return true;
260         }
261     };
262
263 public:
264
265     typedef test_object<Data> object;
266
267     /**
268      * Creates and registers test group with specified name.
269      */
270     test_group(const char* name)
271         : name_(name)
272     {
273         // register itself
274         runner.get().register_group(name_,this);
275
276         // register all tests
277         tests_registerer<object, test_group, MaxTestsInGroup>::reg(*this);
278     }
279
280     /**
281      * This constructor is used in self-test run only.
282      */
283     test_group(const char* name, test_runner& another_runner)
284         : name_(name)
285     {
286         // register itself
287         another_runner.register_group(name_, this);
288
289         // register all tests
290         tests_registerer<test_object<Data>, test_group,
291             MaxTestsInGroup>::reg(*this);
292     };
293
294     /**
295      * Registers test method under given number.
296      */
297     void reg(int n, testmethod tm)
298     {
299         tests_[n] = tm;
300     }
301
302     /**
303      * Reset test position before first test.
304      */
305     void rewind()
306     {
307         current_test_ = tests_.begin();
308     }
309
310     /**
311      * Runs next test.
312      */
313     bool run_next(test_result &tr)
314     {
315         if (current_test_ == tests_.end())
316         {
317             return false;
318         }
319
320         // find next user-specialized test
321         safe_holder<object> obj;
322         while (current_test_ != tests_.end())
323         {
324             tests_iterator current_test = current_test_++;
325
326             if(run_test_(current_test, obj, tr) && tr.result != test_result::dummy)
327             {
328                 return true;
329             }
330         }
331
332         return false;
333     }
334
335     /**
336      * Runs one test by position.
337      */
338     bool run_test(int n, test_result &tr)
339     {
340         if (tests_.rbegin() == tests_.rend() ||
341             tests_.rbegin()->first < n)
342         {
343             return false;
344         }
345
346         // withing scope; check if given test exists
347         tests_iterator ti = tests_.find(n);
348         if (ti == tests_.end())
349         {
350             return false;
351         }
352
353         safe_holder<object> obj;
354         return run_test_(ti, obj, tr);
355     }
356
357     /**
358      * VC allows only one exception handling type per function,
359      * so I have to split the method.
360      */
361     bool run_test_(const tests_iterator& ti, safe_holder<object>& obj, test_result &tr)
362     {
363         std::string current_test_name;
364
365         tr = test_result(name_, ti->first, current_test_name, test_result::ok);
366
367         try
368         {
369             switch (run_test_seh_(ti->second, obj, current_test_name, ti->first))
370                         {
371                                 case SEH_CTOR:
372                                         throw bad_ctor("seh");
373                                         break;
374
375                                 case SEH_TEST:
376                                         throw seh("seh");
377                                         break;
378
379                                 case SEH_DUMMY:
380                                         tr.result = test_result::dummy;
381                                         break;
382
383                                 case SEH_OK:
384                                         // ok
385                                         break;
386             }
387         }
388         catch (const rethrown& ex)
389         {
390             tr = ex.tr;
391             tr.result = test_result::rethrown;
392         }
393         catch (const tut_error& ex)
394         {
395             tr.result = ex.result();
396             tr.exception_typeid = typeid(ex).name();
397             tr.message = ex.what();
398         }
399         catch (const std::exception& ex)
400         {
401             tr.result = test_result::ex;
402             tr.exception_typeid = typeid(ex).name();
403             tr.message = ex.what();
404         }
405         catch (...)
406         {
407             // test failed with unknown exception
408             tr.result = test_result::ex;
409         }
410
411         if (obj.get())
412         {
413             tr.name = obj->get_test_name();
414
415             // try to report to parent, if exists
416             send_result_(obj.get(), tr);
417         }
418         else
419         {
420             tr.name = current_test_name;
421         }
422
423         return true;
424     }
425
426     /**
427      * Runs one under SEH if platform supports it.
428      */
429     seh_result run_test_seh_(testmethod tm, safe_holder<object>& obj,
430                              std::string& current_test_name, int current_test_id)
431     {
432 #if defined(TUT_USE_SEH)
433         __try
434         {
435 #endif
436         if (obj.get() == 0)
437         {
438             reset_holder_(obj);
439         }
440
441         obj->called_method_was_a_dummy_test_ = false;
442
443 #if defined(TUT_USE_SEH)
444
445             __try
446             {
447 #endif
448                 obj.get()->set_test_id(current_test_id);
449                 (obj.get()->*tm)();
450 #if defined(TUT_USE_SEH)
451             }
452             __except(handle_seh_(::GetExceptionCode()))
453             {
454                 current_test_name = obj->get_test_name();
455                 return SEH_TEST;
456             }
457 #endif
458
459         if (obj->called_method_was_a_dummy_test_)
460         {
461             // do not call obj.release(); reuse object
462             return SEH_DUMMY;
463         }
464
465         current_test_name = obj->get_test_name();
466         obj.permit_throw();
467         obj.release();
468 #if defined(TUT_USE_SEH)
469         }
470         __except(handle_seh_(::GetExceptionCode()))
471         {
472                         return SEH_CTOR;
473         }
474 #endif
475         return SEH_OK;
476     }
477
478     void reset_holder_(safe_holder<object>& obj)
479     {
480         try
481         {
482             obj.reset();
483         }
484         catch (const std::exception& ex)
485         {
486             throw bad_ctor(ex.what());
487         }
488         catch (...)
489         {
490             throw bad_ctor("test constructor has generated an exception;"
491                 " group execution is terminated");
492         }
493     }
494 };
495
496 #if defined(TUT_USE_SEH)
497 /**
498  * Decides should we execute handler or ignore SE.
499  */
500 inline int handle_seh_(DWORD excode)
501 {
502     switch(excode)
503     {
504     case EXCEPTION_ACCESS_VIOLATION:
505     case EXCEPTION_DATATYPE_MISALIGNMENT:
506     case EXCEPTION_BREAKPOINT:
507     case EXCEPTION_SINGLE_STEP:
508     case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
509     case EXCEPTION_FLT_DENORMAL_OPERAND:
510     case EXCEPTION_FLT_DIVIDE_BY_ZERO:
511     case EXCEPTION_FLT_INEXACT_RESULT:
512     case EXCEPTION_FLT_INVALID_OPERATION:
513     case EXCEPTION_FLT_OVERFLOW:
514     case EXCEPTION_FLT_STACK_CHECK:
515     case EXCEPTION_FLT_UNDERFLOW:
516     case EXCEPTION_INT_DIVIDE_BY_ZERO:
517     case EXCEPTION_INT_OVERFLOW:
518     case EXCEPTION_PRIV_INSTRUCTION:
519     case EXCEPTION_IN_PAGE_ERROR:
520     case EXCEPTION_ILLEGAL_INSTRUCTION:
521     case EXCEPTION_NONCONTINUABLE_EXCEPTION:
522     case EXCEPTION_STACK_OVERFLOW:
523     case EXCEPTION_INVALID_DISPOSITION:
524     case EXCEPTION_GUARD_PAGE:
525     case EXCEPTION_INVALID_HANDLE:
526         return EXCEPTION_EXECUTE_HANDLER;
527     };
528
529     return EXCEPTION_CONTINUE_SEARCH;
530 }
531 #endif
532 }
533
534 #endif
535