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
9 /**
10 * Template Unit Tests Framework for C++.
11 * http://tut.dozen.ru
12 *
13 * Optional restartable wrapper for test_runner. Allows to restart test runs
14 * finished due to abnormal 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 */
escape(const std::string & orig)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 */
unescape(const std::string & orig)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 */
serialize(std::ostream & os,const tut::test_result & tr)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 default:
119 throw std::logic_error("operator << : bad result_type");
120 }
121 os << ' ' << escape(tr.message) << std::endl;
122 }
123
124 /**
125 * deserialization for test_result
126 */
deserialize(std::istream & is,tut::test_result & tr)127 void deserialize(std::istream& is, tut::test_result& tr)
128 {
129 std::getline(is,tr.group);
130 if (is.eof())
131 {
132 throw tut::no_more_tests();
133 }
134 tr.group = unescape(tr.group);
135
136 tr.test = -1;
137 is >> tr.test;
138 if (tr.test < 0)
139 {
140 throw std::logic_error("operator >> : bad test number");
141 }
142
143 int n = -1;
144 is >> n;
145 switch(n)
146 {
147 case 0:
148 tr.result = test_result::ok;
149 break;
150 case 1:
151 tr.result = test_result::fail;
152 break;
153 case 2:
154 tr.result = test_result::ex;
155 break;
156 case 3:
157 tr.result = test_result::warn;
158 break;
159 case 4:
160 tr.result = test_result::term;
161 break;
162 default:
163 throw std::logic_error("operator >> : bad result_type");
164 }
165
166 is.ignore(1); // space
167 std::getline(is,tr.message);
168 tr.message = unescape(tr.message);
169 if (!is.good())
170 {
171 throw std::logic_error("malformed test result");
172 }
173 }
174 };
175
176 /**
177 * Restartable test runner wrapper.
178 */
179 class restartable_wrapper
180 {
181 test_runner& runner_;
182 callback* callback_;
183
184 std::string dir_;
185 std::string log_; // log file: last test being executed
186 std::string jrn_; // journal file: results of all executed tests
187
188 public:
189 /**
190 * Default constructor.
191 * @param dir Directory where to search/put log and journal files
192 */
restartable_wrapper(const std::string & dir=".")193 restartable_wrapper(const std::string& dir = ".")
194 : runner_(runner.get()),
195 callback_(0),
196 dir_(dir)
197 {
198 // dozen: it works, but it would be better to use system path separator
199 jrn_ = dir_ + '/' + "journal.tut";
200 log_ = dir_ + '/' + "log.tut";
201 }
202
203 /**
204 * Stores another group for getting by name.
205 */
register_group(const std::string & name,group_base * gr)206 void register_group(const std::string& name, group_base* gr)
207 {
208 runner_.register_group(name,gr);
209 }
210
211 /**
212 * Stores callback object.
213 */
set_callback(callback * cb)214 void set_callback(callback* cb)
215 {
216 callback_ = cb;
217 }
218
219 /**
220 * Returns callback object.
221 */
get_callback() const222 callback& get_callback() const
223 {
224 return runner_.get_callback();
225 }
226
227 /**
228 * Returns list of known test groups.
229 */
list_groups() const230 groupnames list_groups() const
231 {
232 return runner_.list_groups();
233 }
234
235 /**
236 * Runs all tests in all groups.
237 */
run_tests() const238 void run_tests() const
239 {
240 // where last run was failed
241 std::string fail_group;
242 int fail_test;
243 read_log_(fail_group,fail_test);
244 bool fail_group_reached = (fail_group == "");
245
246 // iterate over groups
247 tut::groupnames gn = list_groups();
248 tut::groupnames::const_iterator gni,gne;
249 gni = gn.begin();
250 gne = gn.end();
251 while (gni != gne)
252 {
253 // skip all groups before one that failed
254 if (!fail_group_reached)
255 {
256 if (*gni != fail_group)
257 {
258 ++gni;
259 continue;
260 }
261 fail_group_reached = true;
262 }
263
264 // first or restarted run
265 int test = (*gni == fail_group && fail_test >= 0) ? fail_test + 1 : 1;
266 while(true)
267 {
268 // last executed test pos
269 register_execution_(*gni,test);
270
271 try
272 {
273 tut::test_result tr = runner_.run_test(*gni,test);
274 register_test_(tr);
275 }
276 catch (const tut::beyond_last_test&)
277 {
278 break;
279 }
280 catch(const tut::no_such_test&)
281 {
282 // it's ok
283 }
284
285 ++test;
286 }
287
288 ++gni;
289 }
290
291 // show final results to user
292 invoke_callback_();
293
294 // truncate files as mark of successful finish
295 truncate_();
296 }
297
298 private:
299 /**
300 * Shows results from journal file.
301 */
invoke_callback_() const302 void invoke_callback_() const
303 {
304 runner_.set_callback(callback_);
305 runner_.get_callback().run_started();
306
307 std::string current_group;
308 std::ifstream ijournal(jrn_.c_str());
309 while (ijournal.good())
310 {
311 // read next test result
312 try
313 {
314 tut::test_result tr;
315 util::deserialize(ijournal,tr);
316 runner_.get_callback().test_completed(tr);
317 }
318 catch (const no_more_tests&)
319 {
320 break;
321 }
322 }
323
324 runner_.get_callback().run_completed();
325 }
326
327 /**
328 * Register test into journal.
329 */
register_test_(const test_result & tr) const330 void register_test_(const test_result& tr) const
331 {
332 std::ofstream ojournal(jrn_.c_str(), std::ios::app);
333 util::serialize(ojournal, tr);
334 ojournal << std::flush;
335 if (!ojournal.good())
336 {
337 throw std::runtime_error("unable to register test result in file "
338 + jrn_);
339 }
340 }
341
342 /**
343 * Mark the fact test going to be executed
344 */
register_execution_(const std::string & grp,int test) const345 void register_execution_(const std::string& grp, int test) const
346 {
347 // last executed test pos
348 std::ofstream olog(log_.c_str());
349 olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
350 if (!olog.good())
351 {
352 throw std::runtime_error("unable to register execution in file "
353 + log_);
354 }
355 }
356
357 /**
358 * Truncate tests.
359 */
truncate_() const360 void truncate_() const
361 {
362 std::ofstream olog(log_.c_str());
363 std::ofstream ojournal(jrn_.c_str());
364 }
365
366 /**
367 * Read log file
368 */
read_log_(std::string & fail_group,int & fail_test) const369 void read_log_(std::string& fail_group, int& fail_test) const
370 {
371 // read failure point, if any
372 std::ifstream ilog(log_.c_str());
373 std::getline(ilog,fail_group);
374 fail_group = util::unescape(fail_group);
375 ilog >> fail_test;
376 if (!ilog.good())
377 {
378 fail_group = "";
379 fail_test = -1;
380 truncate_();
381 }
382 else
383 {
384 // test was terminated...
385 tut::test_result tr(fail_group, fail_test, "", tut::test_result::term);
386 register_test_(tr);
387 }
388 }
389 };
390
391 }
392
393 #endif
394
395