mirror of https://github.com/coolaj86/fizzbuzz.git
395 lines
8.6 KiB
C++
395 lines
8.6 KiB
C++
|
#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
|
||
|
|