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