]> git.stg.codes - stg.git/blob - tests/tut/tut_restartable.hpp
Simplified module interfaces.
[stg.git] / tests / tut / tut_restartable.hpp
1 #ifndef TUT_RESTARTABLE_H_GUARD
2 #define TUT_RESTARTABLE_H_GUARD
3
4 #include <tut/tut.hpp>
5 #include <fstream>
6 #include <iostream>
7 #include <stdexcept>
8 #include <cassert>
9
10 /**
11  * Optional restartable wrapper for test_runner.
12  *
13  * Allows to restart test runs finished due to abnormal
14  * test application termination (such as segmentation
15  * fault or math error).
16  *
17  * @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
18  */
19
20 namespace tut
21 {
22
23 namespace util
24 {
25
26 /**
27  * Escapes non-alphabetical characters in string.
28  */
29 std::string escape(const std::string& orig)
30 {
31     std::string rc;
32     std::string::const_iterator i,e;
33     i = orig.begin();
34     e = orig.end();
35
36     while (i != e)
37     {
38         if ((*i >= 'a' && *i <= 'z') ||
39                 (*i >= 'A' && *i <= 'Z') ||
40                 (*i >= '0' && *i <= '9') )
41         {
42             rc += *i;
43         }
44         else
45         {
46             rc += '\\';
47             rc += ('a'+(((unsigned int)*i) >> 4));
48             rc += ('a'+(((unsigned int)*i) & 0xF));
49         }
50
51         ++i;
52     }
53     return rc;
54 }
55
56 /**
57  * Un-escapes string.
58  */
59 std::string unescape(const std::string& orig)
60 {
61     std::string rc;
62     std::string::const_iterator i,e;
63     i = orig.begin();
64     e = orig.end();
65
66     while (i != e)
67     {
68         if (*i != '\\')
69         {
70             rc += *i;
71         }
72         else
73         {
74             ++i;
75             if (i == e)
76             {
77                 throw std::invalid_argument("unexpected end of string");
78             }
79             unsigned int c1 = *i;
80             ++i;
81             if (i == e)
82             {
83                 throw std::invalid_argument("unexpected end of string");
84             }
85             unsigned int c2 = *i;
86             rc += (((c1 - 'a') << 4) + (c2 - 'a'));
87         }
88
89         ++i;
90     }
91     return rc;
92 }
93
94 /**
95  * Serialize test_result avoiding interfering with operator <<.
96  */
97 void serialize(std::ostream& os, const tut::test_result& tr)
98 {
99     os << escape(tr.group) << std::endl;
100     os << tr.test << ' ';
101     switch(tr.result)
102     {
103     case test_result::ok:
104         os << 0;
105         break;
106     case test_result::fail:
107         os << 1;
108         break;
109     case test_result::ex:
110         os << 2;
111         break;
112     case test_result::warn:
113         os << 3;
114         break;
115     case test_result::term:
116         os << 4;
117         break;
118     case test_result::rethrown:
119         os << 5;
120         break;
121     case test_result::ex_ctor:
122         os << 6;
123         break;
124     case test_result::dummy:
125         assert(!"Should never be called");
126     default:
127         throw std::logic_error("operator << : bad result_type");
128     }
129     os << ' ' << escape(tr.message) << std::endl;
130 }
131
132 /**
133  * deserialization for test_result
134  */
135 bool deserialize(std::istream& is, tut::test_result& tr)
136 {
137     std::getline(is,tr.group);
138     if (is.eof())
139     {
140         return false;
141     }
142     tr.group = unescape(tr.group);
143
144     tr.test = -1;
145     is >> tr.test;
146     if (tr.test < 0)
147     {
148         throw std::logic_error("operator >> : bad test number");
149     }
150
151     int n = -1;
152     is >> n;
153     switch(n)
154     {
155     case 0:
156         tr.result = test_result::ok;
157         break;
158     case 1:
159         tr.result = test_result::fail;
160         break;
161     case 2:
162         tr.result = test_result::ex;
163         break;
164     case 3:
165         tr.result = test_result::warn;
166         break;
167     case 4:
168         tr.result = test_result::term;
169         break;
170     case 5:
171         tr.result = test_result::rethrown;
172         break;
173     case 6:
174         tr.result = test_result::ex_ctor;
175         break;
176     default:
177         throw std::logic_error("operator >> : bad result_type");
178     }
179
180     is.ignore(1); // space
181     std::getline(is,tr.message);
182     tr.message = unescape(tr.message);
183     if (!is.good())
184     {
185         throw std::logic_error("malformed test result");
186     }
187     return true;
188 }
189 }
190
191 /**
192  * Restartable test runner wrapper.
193  */
194 class restartable_wrapper
195 {
196     test_runner& runner_;
197     callbacks callbacks_;
198
199     std::string dir_;
200     std::string log_; // log file: last test being executed
201     std::string jrn_; // journal file: results of all executed tests
202
203 public:
204     /**
205      * Default constructor.
206      * @param dir Directory where to search/put log and journal files
207      */
208     restartable_wrapper(const std::string& dir = ".")
209         : runner_(runner.get()),
210           callbacks_(),
211           dir_(dir),
212           log_( dir + '/' + "log.tut" ),
213           jrn_( dir + '/' + "journal.tut" )
214     {
215         // dozen: it works, but it would be better to use system path separator
216     }
217
218     /**
219      * Stores another group for getting by name.
220      */
221     void register_group(const std::string& name, group_base* gr)
222     {
223         runner_.register_group(name,gr);
224     }
225
226     /**
227      * Stores callback object.
228      */
229     void set_callback(callback* cb)
230     {
231         callbacks_.clear();
232         callbacks_.insert(cb);
233     }
234
235     void insert_callback(callback* cb)
236     {
237         callbacks_.insert(cb);
238     }
239
240     void erase_callback(callback* cb)
241     {
242         callbacks_.erase(cb);
243     }
244
245     void set_callbacks(const callbacks& cb)
246     {
247         callbacks_ = cb;
248     }
249
250     const callbacks& get_callbacks() const
251     {
252         return runner_.get_callbacks();
253     }
254
255     /**
256      * Returns list of known test groups.
257      */
258     groupnames list_groups() const
259     {
260         return runner_.list_groups();
261     }
262
263     /**
264      * Runs all tests in all groups.
265      */
266     void run_tests() const
267     {
268         // where last run was failed
269         std::string fail_group;
270         int fail_test;
271         read_log_(fail_group,fail_test);
272         bool fail_group_reached = (fail_group == "");
273
274         // iterate over groups
275         tut::groupnames gn = list_groups();
276         tut::groupnames::const_iterator gni,gne;
277         gni = gn.begin();
278         gne = gn.end();
279         while (gni != gne)
280         {
281             // skip all groups before one that failed
282             if (!fail_group_reached)
283             {
284                 if (*gni != fail_group)
285                 {
286                     ++gni;
287                     continue;
288                 }
289                 fail_group_reached = true;
290             }
291
292             // first or restarted run
293             int test = (*gni == fail_group && fail_test >= 0) ? fail_test + 1 : 1;
294             while(true)
295             {
296                 // last executed test pos
297                 register_execution_(*gni,test);
298
299                 tut::test_result tr;
300                 if( !runner_.run_test(*gni,test, tr) || tr.result == test_result::dummy )
301                 {
302                     break;
303                 }
304                 register_test_(tr);
305
306                 ++test;
307             }
308
309             ++gni;
310         }
311
312         // show final results to user
313         invoke_callback_();
314
315         // truncate files as mark of successful finish
316         truncate_();
317     }
318
319 private:
320     /**
321      * Shows results from journal file.
322      */
323     void invoke_callback_() const
324     {
325         runner_.set_callbacks(callbacks_);
326         runner_.cb_run_started_();
327
328         std::string current_group;
329         std::ifstream ijournal(jrn_.c_str());
330         while (ijournal.good())
331         {
332             tut::test_result tr;
333             if( !util::deserialize(ijournal,tr) )
334             {
335                 break;
336             }
337             runner_.cb_test_completed_(tr);
338         }
339
340         runner_.cb_run_completed_();
341     }
342
343     /**
344      * Register test into journal.
345      */
346     void register_test_(const test_result& tr) const
347     {
348         std::ofstream ojournal(jrn_.c_str(), std::ios::app);
349         util::serialize(ojournal, tr);
350         ojournal << std::flush;
351         if (!ojournal.good())
352         {
353             throw std::runtime_error("unable to register test result in file "
354                 + jrn_);
355         }
356     }
357
358     /**
359      * Mark the fact test going to be executed
360      */
361     void register_execution_(const std::string& grp, int test) const
362     {
363         // last executed test pos
364         std::ofstream olog(log_.c_str());
365         olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
366         if (!olog.good())
367         {
368             throw std::runtime_error("unable to register execution in file "
369                 + log_);
370         }
371     }
372
373     /**
374      * Truncate tests.
375      */
376     void truncate_() const
377     {
378         std::ofstream olog(log_.c_str());
379         std::ofstream ojournal(jrn_.c_str());
380     }
381
382     /**
383      * Read log file
384      */
385     void read_log_(std::string& fail_group, int& fail_test) const
386     {
387         // read failure point, if any
388         std::ifstream ilog(log_.c_str());
389         std::getline(ilog,fail_group);
390         fail_group = util::unescape(fail_group);
391         ilog >> fail_test;
392         if (!ilog.good())
393         {
394             fail_group = "";
395             fail_test = -1;
396             truncate_();
397         }
398         else
399         {
400             // test was terminated...
401             tut::test_result tr(fail_group, fail_test, "", tut::test_result::term);
402             register_test_(tr);
403         }
404     }
405 };
406
407 }
408
409 #endif
410