wibble  1.1
tut_restartable.h
Go to the documentation of this file.
1 #ifndef TUT_RESTARTABLE_H_GUARD
2 #define TUT_RESTARTABLE_H_GUARD
3 
4 #include <wibble/tests/tut.h>
5 #include <fstream>
6 #include <iostream>
7 
19 namespace tut
20 {
21  namespace util
22  {
26  std::string escape(const std::string& orig)
27  {
28  std::string rc;
29  std::string::const_iterator i,e;
30  i = orig.begin();
31  e = orig.end();
32 
33  while( i != e )
34  {
35  if( (*i >= 'a' && *i <= 'z') ||
36  (*i >= 'A' && *i <= 'Z') ||
37  (*i >= '0' && *i <= '9') )
38  {
39  rc += *i;
40  }
41  else
42  {
43  rc += '\\';
44  rc += ('a'+(((unsigned int)*i)>>4));
45  rc += ('a'+(((unsigned int)*i)&0xF));
46  }
47 
48  ++i;
49  }
50  return rc;
51  }
52 
56  std::string unescape(const std::string& orig)
57  {
58  std::string rc;
59  std::string::const_iterator i,e;
60  i = orig.begin();
61  e = orig.end();
62 
63  while( i != e )
64  {
65  if( *i != '\\' )
66  {
67  rc += *i;
68  }
69  else
70  {
71  ++i; if( i == e ) throw std::invalid_argument("unexpected end of string");
72  unsigned int c1 = *i;
73  ++i; if( i == e ) throw std::invalid_argument("unexpected end of string");
74  unsigned int c2 = *i;
75  rc += (((c1-'a')<<4) + (c2-'a'));
76  }
77 
78  ++i;
79  }
80  return rc;
81  }
82 
86  void serialize(std::ostream& os,const tut::test_result& tr)
87  {
88  os << escape(tr.group) << std::endl;
89  os << tr.test << ' ';
90  switch(tr.result)
91  {
92  case test_result::ok: os << 0; break;
93  case test_result::fail: os << 1; break;
94  case test_result::ex: os << 2; break;
95  case test_result::warn: os << 3; break;
96  case test_result::term: os << 4; break;
97  default: throw std::logic_error("operator << : bad result_type");
98  }
99  os << ' ' << escape(tr.message) << std::endl;
100  }
101 
105  void deserialize(std::istream& is,tut::test_result& tr)
106  {
107  std::getline(is,tr.group);
108  if( is.eof() ) throw tut::no_more_tests();
109  tr.group = unescape(tr.group);
110 
111  tr.test = -1;
112  is >> tr.test;
113  if( tr.test < 0 ) throw std::logic_error("operator >> : bad test number");
114 
115  int n = -1; is >> n;
116  switch(n)
117  {
118  case 0: tr.result = test_result::ok; break;
119  case 1: tr.result = test_result::fail; break;
120  case 2: tr.result = test_result::ex; break;
121  case 3: tr.result = test_result::warn; break;
122  case 4: tr.result = test_result::term; break;
123  default: throw std::logic_error("operator >> : bad result_type");
124  }
125 
126  is.ignore(1); // space
127  std::getline(is,tr.message);
128  tr.message = unescape(tr.message);
129  if( !is.good() ) throw std::logic_error("malformed test result");
130  }
131  };
132 
137  {
138  test_runner& runner_;
139  callback* callback_;
140 
141  std::string dir_;
142  std::string log_; // log file: last test being executed
143  std::string jrn_; // journal file: results of all executed tests
144 
145  public:
150  restartable_wrapper(const std::string& dir = ".")
151  : runner_(runner.get()), callback_(0), dir_(dir)
152  {
153  // dozen: it works, but it would be better to use system path separator
154  jrn_ = dir_+'/'+"journal.tut";
155  log_ = dir_+'/'+"log.tut";
156  }
157 
161  void register_group(const std::string& name,group_base* gr)
162  {
163  runner_.register_group(name,gr);
164  }
165 
170  {
171  callback_ = cb;
172  }
173 
178  {
179  return runner_.get_callback();
180  }
181 
186  {
187  return runner_.list_groups();
188  }
189 
193  void run_tests() const
194  {
195  // where last run was failed
196  std::string fail_group;
197  int fail_test;
198  read_log_(fail_group,fail_test);
199  bool fail_group_reached = (fail_group == "");
200 
201  // iterate over groups
203  tut::groupnames::const_iterator gni,gne;
204  gni = gn.begin();
205  gne = gn.end();
206  while( gni != gne )
207  {
208  // skip all groups before one that failed
209  if( !fail_group_reached )
210  {
211  if( *gni != fail_group )
212  {
213  ++gni;
214  continue;
215  }
216  fail_group_reached = true;
217  }
218 
219  // first or restarted run
220  int test = (*gni == fail_group && fail_test>=0)? fail_test+1:1;
221  while(true)
222  {
223  // last executed test pos
224  register_execution_(*gni,test);
225 
226  try
227  {
228  tut::test_result tr = runner_.run_test(*gni,test);
229  register_test_(tr);
230  }
231  catch( const tut::beyond_last_test& ex )
232  {
233  break;
234  }
235  catch( const tut::no_such_test& ex )
236  {
237  // it's ok
238  }
239 
240  ++test;
241  }
242 
243  ++gni;
244  }
245 
246  // show final results to user
247  invoke_callback_();
248 
249  // truncate files as mark of successful finish
250  truncate_();
251  }
252 
253  private:
257  void invoke_callback_() const
258  {
259  runner_.set_callback(callback_);
260  runner_.get_callback().run_started();
261 
262  std::string current_group;
263  std::ifstream ijournal(jrn_.c_str());
264  while( ijournal.good() )
265  {
266  // read next test result
267  try
268  {
269  tut::test_result tr;
270  util::deserialize(ijournal,tr);
271  runner_.get_callback().test_completed(tr);
272  }
273  catch( const no_more_tests& )
274  {
275  break;
276  }
277  }
278 
279  runner_.get_callback().run_completed();
280  }
281 
285  void register_test_(const test_result& tr) const
286  {
287  std::ofstream ojournal(jrn_.c_str(),std::ios::app);
288  util::serialize(ojournal,tr);
289  ojournal << std::flush;
290  if( !ojournal.good() ) throw std::runtime_error("unable to register test result in file "+jrn_);
291  }
292 
296  void register_execution_(const std::string& grp,int test) const
297  {
298  // last executed test pos
299  std::ofstream olog(log_.c_str());
300  olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
301  if( !olog.good() ) throw std::runtime_error("unable to register execution in file "+log_);
302  }
303 
307  void truncate_() const
308  {
309  std::ofstream olog(log_.c_str());
310  std::ofstream ojournal(jrn_.c_str());
311  }
312 
316  void read_log_(std::string& fail_group,int& fail_test) const
317  {
318  // read failure point, if any
319  std::ifstream ilog(log_.c_str());
320  std::getline(ilog,fail_group);
321  fail_group = util::unescape(fail_group);
322  ilog >> fail_test;
323  if( !ilog.good() )
324  {
325  fail_group = ""; fail_test = -1;
326  truncate_();
327  }
328  else
329  {
330  // test was terminated...
331  tut::test_result tr(fail_group,fail_test,tut::test_result::term);
332  register_test_(tr);
333  }
334  }
335  };
336 }
337 
338 #endif
339