00001 #ifndef TUT_RESTARTABLE_H_GUARD
00002 #define TUT_RESTARTABLE_H_GUARD
00003
00004 #include "tut.h"
00005 #include <fstream>
00006 #include <iostream>
00007
00019 namespace tut
00020 {
00021 namespace util
00022 {
00026 std::string escape(const std::string& orig)
00027 {
00028 std::string rc;
00029 std::string::const_iterator i,e;
00030 i = orig.begin();
00031 e = orig.end();
00032
00033 while( i != e )
00034 {
00035 if( (*i >= 'a' && *i <= 'z') ||
00036 (*i >= 'A' && *i <= 'Z') ||
00037 (*i >= '0' && *i <= '9') )
00038 {
00039 rc += *i;
00040 }
00041 else
00042 {
00043 rc += '\\';
00044 rc += ('a'+(((unsigned int)*i)>>4));
00045 rc += ('a'+(((unsigned int)*i)&0xF));
00046 }
00047
00048 ++i;
00049 }
00050 return rc;
00051 }
00052
00056 std::string unescape(const std::string& orig)
00057 {
00058 std::string rc;
00059 std::string::const_iterator i,e;
00060 i = orig.begin();
00061 e = orig.end();
00062
00063 while( i != e )
00064 {
00065 if( *i != '\\' )
00066 {
00067 rc += *i;
00068 }
00069 else
00070 {
00071 ++i; if( i == e ) throw std::invalid_argument("unexpected end of string");
00072 unsigned int c1 = *i;
00073 ++i; if( i == e ) throw std::invalid_argument("unexpected end of string");
00074 unsigned int c2 = *i;
00075 rc += (((c1-'a')<<4) + (c2-'a'));
00076 }
00077
00078 ++i;
00079 }
00080 return rc;
00081 }
00082
00086 void serialize(std::ostream& os,const tut::test_result& tr)
00087 {
00088 os << escape(tr.group) << std::endl;
00089 os << tr.test << ' ';
00090 switch(tr.result)
00091 {
00092 case test_result::ok: os << 0; break;
00093 case test_result::fail: os << 1; break;
00094 case test_result::ex: os << 2; break;
00095 case test_result::warn: os << 3; break;
00096 case test_result::term: os << 4; break;
00097 default: throw std::logic_error("operator << : bad result_type");
00098 }
00099 os << ' ' << escape(tr.message) << std::endl;
00100 }
00101
00105 void deserialize(std::istream& is,tut::test_result& tr)
00106 {
00107 std::getline(is,tr.group);
00108 if( is.eof() ) throw tut::no_more_tests();
00109 tr.group = unescape(tr.group);
00110
00111 tr.test = -1;
00112 is >> tr.test;
00113 if( tr.test < 0 ) throw std::logic_error("operator >> : bad test number");
00114
00115 int n = -1; is >> n;
00116 switch(n)
00117 {
00118 case 0: tr.result = test_result::ok; break;
00119 case 1: tr.result = test_result::fail; break;
00120 case 2: tr.result = test_result::ex; break;
00121 case 3: tr.result = test_result::warn; break;
00122 case 4: tr.result = test_result::term; break;
00123 default: throw std::logic_error("operator >> : bad result_type");
00124 }
00125
00126 is.ignore(1);
00127 std::getline(is,tr.message);
00128 tr.message = unescape(tr.message);
00129 if( !is.good() ) throw std::logic_error("malformed test result");
00130 }
00131 };
00132
00136 class restartable_wrapper
00137 {
00138 test_runner& runner_;
00139 callback* callback_;
00140
00141 std::string dir_;
00142 std::string log_;
00143 std::string jrn_;
00144
00145 public:
00150 restartable_wrapper(const std::string& dir = ".")
00151 : runner_(runner.get()), callback_(0), dir_(dir)
00152 {
00153
00154 jrn_ = dir_+'/'+"journal.tut";
00155 log_ = dir_+'/'+"log.tut";
00156 }
00157
00161 void register_group(const std::string& name,group_base* gr)
00162 {
00163 runner_.register_group(name,gr);
00164 }
00165
00169 void set_callback(callback* cb)
00170 {
00171 callback_ = cb;
00172 }
00173
00177 callback& get_callback() const
00178 {
00179 return runner_.get_callback();
00180 }
00181
00185 groupnames list_groups() const
00186 {
00187 return runner_.list_groups();
00188 }
00189
00193 void run_tests() const
00194 {
00195
00196 std::string fail_group;
00197 int fail_test;
00198 read_log_(fail_group,fail_test);
00199 bool fail_group_reached = (fail_group == "");
00200
00201
00202 tut::groupnames gn = list_groups();
00203 tut::groupnames::const_iterator gni,gne;
00204 gni = gn.begin();
00205 gne = gn.end();
00206 while( gni != gne )
00207 {
00208
00209 if( !fail_group_reached )
00210 {
00211 if( *gni != fail_group )
00212 {
00213 ++gni;
00214 continue;
00215 }
00216 fail_group_reached = true;
00217 }
00218
00219
00220 int test = (*gni == fail_group && fail_test>=0)? fail_test+1:1;
00221 while(true)
00222 {
00223
00224 register_execution_(*gni,test);
00225
00226 try
00227 {
00228 tut::test_result tr = runner_.run_test(*gni,test);
00229 register_test_(tr);
00230 }
00231 catch( const tut::beyond_last_test& ex )
00232 {
00233 break;
00234 }
00235 catch( const tut::no_such_test& ex )
00236 {
00237
00238 }
00239
00240 ++test;
00241 }
00242
00243 ++gni;
00244 }
00245
00246
00247 invoke_callback_();
00248
00249
00250 truncate_();
00251 }
00252
00253 private:
00257 void invoke_callback_() const
00258 {
00259 runner_.set_callback(callback_);
00260 runner_.get_callback().run_started();
00261
00262 std::string current_group;
00263 std::ifstream ijournal(jrn_.c_str());
00264 while( ijournal.good() )
00265 {
00266
00267 try
00268 {
00269 tut::test_result tr;
00270 util::deserialize(ijournal,tr);
00271 runner_.get_callback().test_completed(tr);
00272 }
00273 catch( const no_more_tests& )
00274 {
00275 break;
00276 }
00277 }
00278
00279 runner_.get_callback().run_completed();
00280 }
00281
00285 void register_test_(const test_result& tr) const
00286 {
00287 std::ofstream ojournal(jrn_.c_str(),std::ios::app);
00288 util::serialize(ojournal,tr);
00289 ojournal << std::flush;
00290 if( !ojournal.good() ) throw std::runtime_error("unable to register test result in file "+jrn_);
00291 }
00292
00296 void register_execution_(const std::string& grp,int test) const
00297 {
00298
00299 std::ofstream olog(log_.c_str());
00300 olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
00301 if( !olog.good() ) throw std::runtime_error("unable to register execution in file "+log_);
00302 }
00303
00307 void truncate_() const
00308 {
00309 std::ofstream olog(log_.c_str());
00310 std::ofstream ojournal(jrn_.c_str());
00311 }
00312
00316 void read_log_(std::string& fail_group,int& fail_test) const
00317 {
00318
00319 std::ifstream ilog(log_.c_str());
00320 std::getline(ilog,fail_group);
00321 fail_group = util::unescape(fail_group);
00322 ilog >> fail_test;
00323 if( !ilog.good() )
00324 {
00325 fail_group = ""; fail_test = -1;
00326 truncate_();
00327 }
00328 else
00329 {
00330
00331 tut::test_result tr(fail_group,fail_test,tut::test_result::term);
00332 register_test_(tr);
00333 }
00334 }
00335 };
00336 }
00337
00338 #endif