libdballe 4.0.18
|
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); // space 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_; // log file: last test being executed 00143 std::string jrn_; // journal file: results of all executed tests 00144 00145 public: 00150 restartable_wrapper(const std::string& dir = ".") 00151 : runner_(runner.get()), callback_(0), dir_(dir) 00152 { 00153 // dozen: it works, but it would be better to use system path separator 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 // where last run was failed 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 // iterate over groups 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 // skip all groups before one that failed 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 // first or restarted run 00220 int test = (*gni == fail_group && fail_test>=0)? fail_test+1:1; 00221 while(true) 00222 { 00223 // last executed test pos 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 // it's ok 00238 } 00239 00240 ++test; 00241 } 00242 00243 ++gni; 00244 } 00245 00246 // show final results to user 00247 invoke_callback_(); 00248 00249 // truncate files as mark of successful finish 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 // read next test result 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 // last executed test pos 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 // read failure point, if any 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 // test was terminated... 00331 tut::test_result tr(fail_group,fail_test,tut::test_result::term); 00332 register_test_(tr); 00333 } 00334 } 00335 }; 00336 } 00337 00338 #endif 00339