]> git.stg.codes - stg.git/blob - tests/tut/tut_restartable.hpp
Make fee charging rules inclusive (2 includes 0 and 3 includes fee)
[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           dir_(dir)
211     {
212         // dozen: it works, but it would be better to use system path separator
213         jrn_ = dir_ + '/' + "journal.tut";
214         log_ = dir_ + '/' + "log.tut";
215     }
216
217     /**
218      * Stores another group for getting by name.
219      */
220     void register_group(const std::string& name, group_base* gr)
221     {
222         runner_.register_group(name,gr);
223     }
224
225     /**
226      * Stores callback object.
227      */
228     void set_callback(callback* cb)
229     {
230         callbacks_.clear();
231         callbacks_.insert(cb);
232     }
233
234     void insert_callback(callback* cb)
235     {
236         callbacks_.insert(cb);
237     }
238
239     void erase_callback(callback* cb)
240     {
241         callbacks_.erase(cb);
242     }
243
244     void set_callbacks(const callbacks& cb)
245     {
246         callbacks_ = cb;
247     }
248
249     const callbacks& get_callbacks() const
250     {
251         return runner_.get_callbacks();
252     }
253
254     /**
255      * Returns list of known test groups.
256      */
257     groupnames list_groups() const
258     {
259         return runner_.list_groups();
260     }
261
262     /**
263      * Runs all tests in all groups.
264      */
265     void run_tests() const
266     {
267         // where last run was failed
268         std::string fail_group;
269         int fail_test;
270         read_log_(fail_group,fail_test);
271         bool fail_group_reached = (fail_group == "");
272
273         // iterate over groups
274         tut::groupnames gn = list_groups();
275         tut::groupnames::const_iterator gni,gne;
276         gni = gn.begin();
277         gne = gn.end();
278         while (gni != gne)
279         {
280             // skip all groups before one that failed
281             if (!fail_group_reached)
282             {
283                 if (*gni != fail_group)
284                 {
285                     ++gni;
286                     continue;
287                 }
288                 fail_group_reached = true;
289             }
290
291             // first or restarted run
292             int test = (*gni == fail_group && fail_test >= 0) ? fail_test + 1 : 1;
293             while(true)
294             {
295                 // last executed test pos
296                 register_execution_(*gni,test);
297
298                 tut::test_result tr;
299                 if( !runner_.run_test(*gni,test, tr) || tr.result == test_result::dummy )
300                 {
301                     break;
302                 }
303                 register_test_(tr);
304
305                 ++test;
306             }
307
308             ++gni;
309         }
310
311         // show final results to user
312         invoke_callback_();
313
314         // truncate files as mark of successful finish
315         truncate_();
316     }
317
318 private:
319     /**
320      * Shows results from journal file.
321      */
322     void invoke_callback_() const
323     {
324         runner_.set_callbacks(callbacks_);
325         runner_.cb_run_started_();
326
327         std::string current_group;
328         std::ifstream ijournal(jrn_.c_str());
329         while (ijournal.good())
330         {
331             tut::test_result tr;
332             if( !util::deserialize(ijournal,tr) )
333             {
334                 break;
335             }
336             runner_.cb_test_completed_(tr);
337         }
338
339         runner_.cb_run_completed_();
340     }
341
342     /**
343      * Register test into journal.
344      */
345     void register_test_(const test_result& tr) const
346     {
347         std::ofstream ojournal(jrn_.c_str(), std::ios::app);
348         util::serialize(ojournal, tr);
349         ojournal << std::flush;
350         if (!ojournal.good())
351         {
352             throw std::runtime_error("unable to register test result in file "
353                 + jrn_);
354         }
355     }
356
357     /**
358      * Mark the fact test going to be executed
359      */
360     void register_execution_(const std::string& grp, int test) const
361     {
362         // last executed test pos
363         std::ofstream olog(log_.c_str());
364         olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
365         if (!olog.good())
366         {
367             throw std::runtime_error("unable to register execution in file "
368                 + log_);
369         }
370     }
371
372     /**
373      * Truncate tests.
374      */
375     void truncate_() const
376     {
377         std::ofstream olog(log_.c_str());
378         std::ofstream ojournal(jrn_.c_str());
379     }
380
381     /**
382      * Read log file
383      */
384     void read_log_(std::string& fail_group, int& fail_test) const
385     {
386         // read failure point, if any
387         std::ifstream ilog(log_.c_str());
388         std::getline(ilog,fail_group);
389         fail_group = util::unescape(fail_group);
390         ilog >> fail_test;
391         if (!ilog.good())
392         {
393             fail_group = "";
394             fail_test = -1;
395             truncate_();
396         }
397         else
398         {
399             // test was terminated...
400             tut::test_result tr(fail_group, fail_test, "", tut::test_result::term);
401             register_test_(tr);
402         }
403     }
404 };
405
406 }
407
408 #endif
409