tut.h
Go to the documentation of this file.
#ifndef TUT_H_GUARD
#define TUT_H_GUARD
#include <iostream>
#include <map>
#include <vector>
#include <string>
#include <sstream>
#include <stdexcept>
#include <typeinfo>
#if defined(TUT_USE_SEH)
#include <windows.h>
#include <winbase.h>
#endif
namespace tut
{
struct no_such_test : public std::logic_error
{
no_such_test() : std::logic_error("no such test"){};
};
struct beyond_last_test : public no_such_test
{
beyond_last_test(){};
};
struct no_such_group : public std::logic_error
{
no_such_group(const std::string& grp) :
std::logic_error(grp){};
};
struct no_more_tests
{
no_more_tests(){};
};
struct bad_ctor : public std::logic_error
{
bad_ctor(const std::string& msg) :
std::logic_error(msg){};
};
class failure : public std::logic_error
{
public:
failure(const std::string& msg) : std::logic_error(msg){};
};
class warning : public std::logic_error
{
public:
warning(const std::string& msg) : std::logic_error(msg){};
};
class seh : public std::logic_error
{
public:
seh(const std::string& msg) : std::logic_error(msg){};
};
struct test_result
{
std::string group;
int test;
typedef enum { ok, fail, ex, warn, term, ex_ctor } result_type;
result_type result;
std::string message;
std::string exception_typeid;
test_result()
: test(0),result(ok)
{
}
test_result( const std::string& grp,int pos,result_type res)
: group(grp),test(pos),result(res)
{
}
test_result( const std::string& grp,int pos,
result_type res,
const std::exception& ex)
: group(grp),test(pos),result(res),
message(ex.what()),exception_typeid(typeid(ex).name())
{
}
};
struct group_base
{
virtual ~group_base(){};
// execute tests iteratively
virtual void rewind() = 0;
virtual test_result run_next() = 0;
// execute one test
virtual test_result run_test(int n) = 0;
};
struct callback
{
virtual ~callback(){};
virtual void run_started(){};
virtual void group_started(const std::string& /*name*/){};
virtual void test_completed(const test_result& /*tr*/){};
virtual void group_completed(const std::string& /*name*/){};
virtual void run_completed(){};
};
typedef std::vector<std::string> groupnames;
class test_runner
{
protected:
typedef std::map<std::string,group_base*> groups;
typedef groups::iterator iterator;
typedef groups::const_iterator const_iterator;
groups groups_;
callback default_callback_;
callback* callback_;
public:
test_runner() : callback_(&default_callback_)
{
}
void register_group(const std::string& name,group_base* gr)
{
if( gr == 0 )
{
throw std::invalid_argument("group shall be non-null");
}
groups::iterator found = groups_.find(name);
if( found != groups_.end() )
{
std::string msg("attempt to add already existent group "+name);
// this exception terminates application so we use cerr also
std::cerr << msg << std::endl;
throw std::logic_error(msg);
}
groups_[name] = gr;
}
void set_callback(callback* cb)
{
callback_ = cb==0? &default_callback_:cb;
}
callback& get_callback() const
{
return *callback_;
}
const groupnames list_groups() const
{
groupnames ret;
const_iterator i = groups_.begin();
const_iterator e = groups_.end();
while( i != e )
{
ret.push_back(i->first);
++i;
}
return ret;
}
void run_tests() const
{
callback_->run_started();
const_iterator i = groups_.begin();
const_iterator e = groups_.end();
while( i != e )
{
callback_->group_started(i->first);
try
{
run_all_tests_in_group_(i);
}
catch( const no_more_tests& )
{
callback_->group_completed(i->first);
}
++i;
}
callback_->run_completed();
}
void run_tests(const std::string& group_name) const
{
callback_->run_started();
const_iterator i = groups_.find(group_name);
if( i == groups_.end() )
{
callback_->run_completed();
throw no_such_group(group_name);
}
callback_->group_started(group_name);
try
{
run_all_tests_in_group_(i);
}
catch( const no_more_tests& )
{
// ok
}
callback_->group_completed(group_name);
callback_->run_completed();
}
test_result run_test(const std::string& group_name,int n) const
{
callback_->run_started();
const_iterator i = groups_.find(group_name);
if( i == groups_.end() )
{
callback_->run_completed();
throw no_such_group(group_name);
}
callback_->group_started(group_name);
try
{
test_result tr = i->second->run_test(n);
callback_->test_completed(tr);
callback_->group_completed(group_name);
callback_->run_completed();
return tr;
}
catch( const beyond_last_test& )
{
callback_->group_completed(group_name);
callback_->run_completed();
throw;
}
catch( const no_such_test& )
{
callback_->group_completed(group_name);
callback_->run_completed();
throw;
}
}
private:
void run_all_tests_in_group_(const_iterator i) const
{
i->second->rewind();
for( ;; )
{
test_result tr = i->second->run_next();
callback_->test_completed(tr);
if( tr.result == test_result::ex_ctor )
{
throw no_more_tests();
}
}
}
};
class test_runner_singleton
{
public:
static test_runner& get()
{
static test_runner tr;
return tr;
}
};
extern test_runner_singleton runner;
template <class Data>
class test_object : public Data
{
public:
test_object(){};
bool called_method_was_a_dummy_test_;
template <int n>
void test()
{
called_method_was_a_dummy_test_ = true;
}
};
namespace
{
void ensure(bool cond)
{
if( !cond ) throw failure("");
}
template<typename T>
void ensure(const T msg,bool cond)
{
if( !cond ) throw failure(msg);
}
template <class T,class Q>
void ensure_equals(const char* msg,const Q& actual,const T& expected)
{
if( expected != actual )
{
std::stringstream ss;
ss << (msg?msg:"") << (msg?": ":"") << "expected " << expected << " actual " << actual;
throw failure(ss.str().c_str());
}
}
template <class T,class Q>
void ensure_equals(const Q& actual,const T& expected)
{
ensure_equals<>(0,actual,expected);
}
template <class T>
void ensure_distance(const char* msg,const T& actual,const T& expected,const T& distance)
{
if( expected-distance >= actual || expected+distance <= actual )
{
std::stringstream ss;
ss << (msg?msg:"") << (msg?": ":"") << "expected [" << expected-distance << ";"
<< expected+distance << "] actual " << actual;
throw failure(ss.str().c_str());
}
}
template <class T>
void ensure_distance(const T& actual,const T& expected,const T& distance)
{
ensure_distance<>(0,actual,expected,distance);
}
void fail(const char* msg="")
{
throw failure(msg);
}
}
template <class Test,class Group,int n>
struct tests_registerer
{
static void reg(Group& group)
{
group.reg(n,&Test::template test<n>);
tests_registerer<Test,Group,n-1>::reg(group);
}
};
template<class Test,class Group>
struct tests_registerer<Test,Group,0>
{
static void reg(Group&){};
};
template <class Data,int MaxTestsInGroup = 50>
class test_group : public group_base
{
const char* name_;
typedef void (test_object<Data>::*testmethod)();
typedef std::map<int,testmethod> tests;
typedef typename tests::iterator tests_iterator;
typedef typename tests::const_iterator tests_const_iterator;
typedef typename tests::const_reverse_iterator
tests_const_reverse_iterator;
typedef typename tests::size_type size_type;
tests tests_;
tests_iterator current_test_;
template <class T>
class safe_holder
{
T* p_;
bool permit_throw_in_dtor;
safe_holder(const safe_holder&);
safe_holder& operator = (const safe_holder&);
public:
safe_holder() : p_(0),permit_throw_in_dtor(false)
{
}
~safe_holder()
{
release();
}
T* operator -> () const { return p_; };
T* get() const { return p_; };
void permit_throw(){ permit_throw_in_dtor = true; }
void release()
{
try
{
if( delete_obj() == false )
{
throw warning("destructor of test object raised an SEH exception");
}
}
catch( const std::exception& ex )
{
if( permit_throw_in_dtor )
{
std::string msg = "destructor of test object raised exception: ";
msg += ex.what();
throw warning(msg);
}
}
catch( ... )
{
if( permit_throw_in_dtor )
{
throw warning("destructor of test object raised an exception");
}
}
}
void reset()
{
release();
permit_throw_in_dtor = false;
p_ = new T();
}
bool delete_obj()
{
#if defined(TUT_USE_SEH)
__try
{
#endif
T* p = p_;
p_ = 0;
delete p;
#if defined(TUT_USE_SEH)
}
__except(handle_seh_(::GetExceptionCode()))
{
if( permit_throw_in_dtor )
{
return false;
}
}
#endif
return true;
}
};
public:
typedef test_object<Data> object;
test_group(const char* name)
: name_(name)
{
// register itself
runner.get().register_group(name_,this);
// register all tests
tests_registerer<object,test_group,MaxTestsInGroup>::reg(*this);
};
test_group(const char* name,test_runner& another_runner)
: name_(name)
{
// register itself
another_runner.register_group(name_,this);
// register all tests
tests_registerer<test_object<Data>,
test_group,MaxTestsInGroup>::reg(*this);
};
void reg(int n,testmethod tm)
{
tests_[n] = tm;
}
void rewind()
{
current_test_ = tests_.begin();
}
test_result run_next()
{
if( current_test_ == tests_.end() )
{
throw no_more_tests();
}
// find next user-specialized test
safe_holder<object> obj;
while( current_test_ != tests_.end() )
{
try
{
return run_test_(current_test_++,obj);
}
catch( const no_such_test& )
{
continue;
}
}
throw no_more_tests();
}
test_result run_test(int n)
{
// beyond tests is special case to discover upper limit
if( tests_.rbegin() == tests_.rend() ) throw beyond_last_test();
if( tests_.rbegin()->first < n ) throw beyond_last_test();
// withing scope; check if given test exists
tests_iterator ti = tests_.find(n);
if( ti == tests_.end() ) throw no_such_test();
safe_holder<object> obj;
return run_test_(ti,obj);
}
private:
test_result run_test_(const tests_iterator& ti,safe_holder<object>& obj)
{
try
{
if( run_test_seh_(ti->second,obj) == false )
throw seh("seh");
}
catch(const no_such_test&)
{
throw;
}
catch(const warning& ex)
{
// test ok, but destructor failed
test_result tr(name_,ti->first,test_result::warn,ex);
return tr;
}
catch(const failure& ex)
{
// test failed because of ensure() or similar method
test_result tr(name_,ti->first,test_result::fail,ex);
return tr;
}
catch(const seh& ex)
{
// test failed with sigsegv, divide by zero, etc
test_result tr(name_,ti->first,test_result::term,ex);
return tr;
}
catch(const bad_ctor& ex)
{
// test failed because test ctor failed; stop the whole group
test_result tr(name_,ti->first,test_result::ex_ctor,ex);
return tr;
}
catch(const std::exception& ex)
{
// test failed with std::exception
test_result tr(name_,ti->first,test_result::ex,ex);
return tr;
}
catch(...)
{
// test failed with unknown exception
test_result tr(name_,ti->first,test_result::ex);
return tr;
}
// test passed
test_result tr(name_,ti->first,test_result::ok);
return tr;
}
bool run_test_seh_(testmethod tm,safe_holder<object>& obj)
{
#if defined(TUT_USE_SEH)
__try
{
#endif
if( obj.get() == 0 )
{
reset_holder_(obj);
}
obj->called_method_was_a_dummy_test_ = false;
#if defined(TUT_USE_SEH)
__try
{
#endif
(obj.get()->*tm)();
#if defined(TUT_USE_SEH)
}
__except(handle_seh_(::GetExceptionCode()))
{
// throw seh("SEH");
return false;
}
#endif
if( obj->called_method_was_a_dummy_test_ )
{
// do not call obj.release(); reuse object
throw no_such_test();
}
obj.permit_throw();
obj.release();
#if defined(TUT_USE_SEH)
}
__except(handle_seh_(::GetExceptionCode()))
{
return false;
}
#endif
return true;
}
void reset_holder_(safe_holder<object>& obj)
{
try
{
obj.reset();
}
catch(const std::exception& ex)
{
throw bad_ctor(ex.what());
}
catch(...)
{
throw bad_ctor("test constructor has generated an exception; group execution is terminated");
}
}
};
#if defined(TUT_USE_SEH)
inline int handle_seh_(DWORD excode)
{
switch(excode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_DATATYPE_MISALIGNMENT:
case EXCEPTION_BREAKPOINT:
case EXCEPTION_SINGLE_STEP:
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
case EXCEPTION_FLT_DENORMAL_OPERAND:
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_FLT_INEXACT_RESULT:
case EXCEPTION_FLT_INVALID_OPERATION:
case EXCEPTION_FLT_OVERFLOW:
case EXCEPTION_FLT_STACK_CHECK:
case EXCEPTION_FLT_UNDERFLOW:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_OVERFLOW:
case EXCEPTION_PRIV_INSTRUCTION:
case EXCEPTION_IN_PAGE_ERROR:
case EXCEPTION_ILLEGAL_INSTRUCTION:
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
case EXCEPTION_STACK_OVERFLOW:
case EXCEPTION_INVALID_DISPOSITION:
case EXCEPTION_GUARD_PAGE:
case EXCEPTION_INVALID_HANDLE:
return EXCEPTION_EXECUTE_HANDLER;
};
return EXCEPTION_CONTINUE_SEARCH;
}
#endif
}
#endif
tut::tests_registerer< Test, Group, 0 >::reg
static void reg(Group &)
Definition: tut.h:558
tut::test_runner::run_tests
void run_tests(const std::string &group_name) const
Runs all tests in specified group.
Definition: tut.h:332
wibble::iterator
Iterator< typename I::value_type > iterator(I i)
Definition: iterator.h:123
tut::test_result::test_result
test_result()
Default constructor.
Definition: tut.h:136
tut::test_object::called_method_was_a_dummy_test_
bool called_method_was_a_dummy_test_
The flag is set to true by default (dummy) test.
Definition: tut.h:439
tut::test_runner
Test runner.
Definition: tut.h:228
tut::no_such_test::no_such_test
no_such_test()
Definition: tut.h:31
tut::test_group::test_group
test_group(const char *name)
Creates and registers test group with specified name.
Definition: tut.h:686
tut::test_result::group
std::string group
Test group name.
Definition: tut.h:110
tut::test_group
Test group; used to recreate test object instance for each new test since we have to have reinitializ...
Definition: tut.h:567
tut::tests_registerer
Walks through test tree and stores address of each test method in group.
Definition: tut.h:546
tut::no_such_test
Exception to be throwed when attempted to execute missed test by number.
Definition: tut.h:29
Test
void Test
Definition: test.h:178
tut::test_result::exception_typeid
std::string exception_typeid
Definition: tut.h:131
tut::test_runner::groups_
groups groups_
Definition: tut.h:234
tut::test_object
Test object.
Definition: tut.h:433
std
tut::no_such_group
Group not found exception.
Definition: tut.h:47
tut::callback::run_completed
virtual void run_completed()
Called when all tests in run completed.
Definition: tut.h:217
tut::tests_registerer::reg
static void reg(Group &group)
Definition: tut.h:548
tut::test_result::ok
Definition: tut.h:124
tut::failure::failure
failure(const std::string &msg)
Definition: tut.h:78
tut::test_runner::const_iterator
groups::const_iterator const_iterator
Definition: tut.h:233
tut::test_result::test_result
test_result(const std::string &grp, int pos, result_type res, const std::exception &ex)
Constructor with exception.
Definition: tut.h:152
tut::test_result::result_type
result_type
ok - test finished successfully fail - test failed with ensure() or fail() methods ex - test throwed ...
Definition: tut.h:124
tut::group_base::rewind
virtual void rewind()=0
tut::failure
Exception to be throwed when ensure() fails or fail() called.
Definition: tut.h:75
tut::warning
Exception to be throwed when test desctructor throwed an exception.
Definition: tut.h:84
tut::beyond_last_test::beyond_last_test
beyond_last_test()
Definition: tut.h:41
tut::test_result::test
int test
Test number in group.
Definition: tut.h:115
tut::warning::warning
warning(const std::string &msg)
Definition: tut.h:87
tut::test_result::ex_ctor
Definition: tut.h:124
tut::test_group::run_test
test_result run_test(int n)
Runs one test by position.
Definition: tut.h:756
tut::callback::test_completed
virtual void test_completed(const test_result &)
Called when a test finished.
Definition: tut.h:206
tut::test_result::result
result_type result
Definition: tut.h:125
ensure
#define ensure(x)
Definition: tests.h:101
tut::no_such_group::no_such_group
no_such_group(const std::string &grp)
Definition: tut.h:49
tut
Template Unit Tests Framework for C++.
Definition: tut-main.cpp:6
tut::test_runner::callback_
callback * callback_
Definition: tut.h:237
tut::test_group::rewind
void rewind()
Reset test position before first test.
Definition: tut.h:721
tut::test_result::warn
Definition: tut.h:124
tut::test_runner::groups
std::map< std::string, group_base * > groups
Definition: tut.h:231
tut::callback::group_completed
virtual void group_completed(const std::string &)
Called when a group is completed.
Definition: tut.h:212
tut::bad_ctor
Internal exception to be throwed when test constructor has failed.
Definition: tut.h:66
tut::runner
test_runner_singleton runner
Definition: tut-main.cpp:8
tut::test_runner::set_callback
void set_callback(callback *cb)
Stores callback object.
Definition: tut.h:272
tut::groupnames
std::vector< std::string > groupnames
Typedef for runner::list_groups()
Definition: tut.h:223
tut::test_runner_singleton
Singleton for test_runner implementation.
Definition: tut.h:416
tut::test_group::object
test_object< Data > object
Definition: tut.h:681
tut::test_group::test_group
test_group(const char *name, test_runner &another_runner)
This constructor is used in self-test run only.
Definition: tut.h:699
tut::test_runner::run_test
test_result run_test(const std::string &group_name, int n) const
Runs one test in specified group.
Definition: tut.h:360
tut::test_runner::default_callback_
callback default_callback_
Definition: tut.h:236
tut::no_more_tests
Internal exception to be throwed when no more tests left in group or journal.
Definition: tut.h:57
tut::test_runner::register_group
void register_group(const std::string &name, group_base *gr)
Stores another group for getting by name.
Definition: tut.h:250
tut::group_base
Interface.
Definition: tut.h:165
tut::test_group::reg
void reg(int n, testmethod tm)
Registers test method under given number.
Definition: tut.h:713
tut::test_runner::run_tests
void run_tests() const
Runs all tests in all groups.
Definition: tut.h:305
tut::group_base::~group_base
virtual ~group_base()
Definition: tut.h:167
tut::test_object::test
void test()
Default do-nothing test.
Definition: tut.h:454
tut::test_result::fail
Definition: tut.h:124
tut::test_runner::get_callback
callback & get_callback() const
Returns callback object.
Definition: tut.h:280
tut::test_runner::list_groups
const groupnames list_groups() const
Returns list of known test groups.
Definition: tut.h:288
tut::test_result::message
std::string message
Exception message for failed test.
Definition: tut.h:130
tut::test_result
Return type of runned test/test group.
Definition: tut.h:105
tut::test_result::test_result
test_result(const std::string &grp, int pos, result_type res)
Constructor.
Definition: tut.h:144
tut::group_base::run_test
virtual test_result run_test(int n)=0
tut::test_result::term
Definition: tut.h:124
tut::test_result::ex
Definition: tut.h:124
ensure_equals
#define ensure_equals(x, y)
Definition: tests.h:105
tut::callback::group_started
virtual void group_started(const std::string &)
Called when a group started.
Definition: tut.h:200
tut::seh
Exception to be throwed when test issued SEH (Win32)
Definition: tut.h:93
tut::no_more_tests::no_more_tests
no_more_tests()
Definition: tut.h:59
tut::test_runner::test_runner
test_runner()
Constructor.
Definition: tut.h:243
tut::test_group::run_next
test_result run_next()
Runs next test.
Definition: tut.h:729
tut::callback::~callback
virtual ~callback()
Virtual destructor is a must for subclassed types.
Definition: tut.h:189
tut::test_runner::iterator
groups::iterator iterator
Definition: tut.h:232
wibble::tests::actual
Actual< A > actual(const A &actual)
Definition: tests.h:384
tut::group_base::run_next
virtual test_result run_next()=0
tut::bad_ctor::bad_ctor
bad_ctor(const std::string &msg)
Definition: tut.h:68
tut::beyond_last_test
No such test and passed test number is higher than any test number in current group.
Definition: tut.h:39
tut::callback
Test runner callback interface.
Definition: tut.h:184
tut::test_object::test_object
test_object()
Default constructor.
Definition: tut.h:439
tut::callback::run_started
virtual void run_started()
Called when new test run started.
Definition: tut.h:194
tut::seh::seh
seh(const std::string &msg)
Definition: tut.h:96