fizzbuzz/linked-list-and-binary-sear.../passoff/tut/tut_restartable.hpp

395 lines
8.6 KiB
C++
Raw Normal View History

#ifndef TUT_RESTARTABLE_H_GUARD
#define TUT_RESTARTABLE_H_GUARD
#include "tut.hpp"
#include <fstream>
#include <iostream>
#include <stdexcept>
/**
* Template Unit Tests Framework for C++.
* http://tut.dozen.ru
*
* Optional restartable wrapper for test_runner. Allows to restart test runs
* finished due to abnormal test application termination (such as segmentation
* fault or math error).
*
* @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
*/
namespace tut
{
namespace util
{
/**
* Escapes non-alphabetical characters in string.
*/
std::string escape(const std::string& orig)
{
std::string rc;
std::string::const_iterator i,e;
i = orig.begin();
e = orig.end();
while (i != e)
{
if ((*i >= 'a' && *i <= 'z') ||
(*i >= 'A' && *i <= 'Z') ||
(*i >= '0' && *i <= '9') )
{
rc += *i;
}
else
{
rc += '\\';
rc += ('a'+(((unsigned int)*i) >> 4));
rc += ('a'+(((unsigned int)*i) & 0xF));
}
++i;
}
return rc;
}
/**
* Un-escapes string.
*/
std::string unescape(const std::string& orig)
{
std::string rc;
std::string::const_iterator i,e;
i = orig.begin();
e = orig.end();
while (i != e)
{
if (*i != '\\')
{
rc += *i;
}
else
{
++i;
if (i == e)
{
throw std::invalid_argument("unexpected end of string");
}
unsigned int c1 = *i;
++i;
if (i == e)
{
throw std::invalid_argument("unexpected end of string");
}
unsigned int c2 = *i;
rc += (((c1 - 'a') << 4) + (c2 - 'a'));
}
++i;
}
return rc;
}
/**
* Serialize test_result avoiding interfering with operator <<.
*/
void serialize(std::ostream& os, const tut::test_result& tr)
{
os << escape(tr.group) << std::endl;
os << tr.test << ' ';
switch(tr.result)
{
case test_result::ok:
os << 0;
break;
case test_result::fail:
os << 1;
break;
case test_result::ex:
os << 2;
break;
case test_result::warn:
os << 3;
break;
case test_result::term:
os << 4;
break;
default:
throw std::logic_error("operator << : bad result_type");
}
os << ' ' << escape(tr.message) << std::endl;
}
/**
* deserialization for test_result
*/
void deserialize(std::istream& is, tut::test_result& tr)
{
std::getline(is,tr.group);
if (is.eof())
{
throw tut::no_more_tests();
}
tr.group = unescape(tr.group);
tr.test = -1;
is >> tr.test;
if (tr.test < 0)
{
throw std::logic_error("operator >> : bad test number");
}
int n = -1;
is >> n;
switch(n)
{
case 0:
tr.result = test_result::ok;
break;
case 1:
tr.result = test_result::fail;
break;
case 2:
tr.result = test_result::ex;
break;
case 3:
tr.result = test_result::warn;
break;
case 4:
tr.result = test_result::term;
break;
default:
throw std::logic_error("operator >> : bad result_type");
}
is.ignore(1); // space
std::getline(is,tr.message);
tr.message = unescape(tr.message);
if (!is.good())
{
throw std::logic_error("malformed test result");
}
}
};
/**
* Restartable test runner wrapper.
*/
class restartable_wrapper
{
test_runner& runner_;
callback* callback_;
std::string dir_;
std::string log_; // log file: last test being executed
std::string jrn_; // journal file: results of all executed tests
public:
/**
* Default constructor.
* @param dir Directory where to search/put log and journal files
*/
restartable_wrapper(const std::string& dir = ".")
: runner_(runner.get()),
callback_(0),
dir_(dir)
{
// dozen: it works, but it would be better to use system path separator
jrn_ = dir_ + '/' + "journal.tut";
log_ = dir_ + '/' + "log.tut";
}
/**
* Stores another group for getting by name.
*/
void register_group(const std::string& name, group_base* gr)
{
runner_.register_group(name,gr);
}
/**
* Stores callback object.
*/
void set_callback(callback* cb)
{
callback_ = cb;
}
/**
* Returns callback object.
*/
callback& get_callback() const
{
return runner_.get_callback();
}
/**
* Returns list of known test groups.
*/
groupnames list_groups() const
{
return runner_.list_groups();
}
/**
* Runs all tests in all groups.
*/
void run_tests() const
{
// where last run was failed
std::string fail_group;
int fail_test;
read_log_(fail_group,fail_test);
bool fail_group_reached = (fail_group == "");
// iterate over groups
tut::groupnames gn = list_groups();
tut::groupnames::const_iterator gni,gne;
gni = gn.begin();
gne = gn.end();
while (gni != gne)
{
// skip all groups before one that failed
if (!fail_group_reached)
{
if (*gni != fail_group)
{
++gni;
continue;
}
fail_group_reached = true;
}
// first or restarted run
int test = (*gni == fail_group && fail_test >= 0) ? fail_test + 1 : 1;
while(true)
{
// last executed test pos
register_execution_(*gni,test);
try
{
tut::test_result tr = runner_.run_test(*gni,test);
register_test_(tr);
}
catch (const tut::beyond_last_test&)
{
break;
}
catch(const tut::no_such_test&)
{
// it's ok
}
++test;
}
++gni;
}
// show final results to user
invoke_callback_();
// truncate files as mark of successful finish
truncate_();
}
private:
/**
* Shows results from journal file.
*/
void invoke_callback_() const
{
runner_.set_callback(callback_);
runner_.get_callback().run_started();
std::string current_group;
std::ifstream ijournal(jrn_.c_str());
while (ijournal.good())
{
// read next test result
try
{
tut::test_result tr;
util::deserialize(ijournal,tr);
runner_.get_callback().test_completed(tr);
}
catch (const no_more_tests&)
{
break;
}
}
runner_.get_callback().run_completed();
}
/**
* Register test into journal.
*/
void register_test_(const test_result& tr) const
{
std::ofstream ojournal(jrn_.c_str(), std::ios::app);
util::serialize(ojournal, tr);
ojournal << std::flush;
if (!ojournal.good())
{
throw std::runtime_error("unable to register test result in file "
+ jrn_);
}
}
/**
* Mark the fact test going to be executed
*/
void register_execution_(const std::string& grp, int test) const
{
// last executed test pos
std::ofstream olog(log_.c_str());
olog << util::escape(grp) << std::endl << test << std::endl << std::flush;
if (!olog.good())
{
throw std::runtime_error("unable to register execution in file "
+ log_);
}
}
/**
* Truncate tests.
*/
void truncate_() const
{
std::ofstream olog(log_.c_str());
std::ofstream ojournal(jrn_.c_str());
}
/**
* Read log file
*/
void read_log_(std::string& fail_group, int& fail_test) const
{
// read failure point, if any
std::ifstream ilog(log_.c_str());
std::getline(ilog,fail_group);
fail_group = util::unescape(fail_group);
ilog >> fail_test;
if (!ilog.good())
{
fail_group = "";
fail_test = -1;
truncate_();
}
else
{
// test was terminated...
tut::test_result tr(fail_group, fail_test, "", tut::test_result::term);
register_test_(tr);
}
}
};
}
#endif