Imported existing code
This commit is contained in:
516
libraries/include/tut/tut.hpp
Normal file
516
libraries/include/tut/tut.hpp
Normal file
@@ -0,0 +1,516 @@
|
||||
#ifndef TUT_H_GUARD
|
||||
#define TUT_H_GUARD
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "tut_exception.hpp"
|
||||
#include "tut_result.hpp"
|
||||
#include "tut_posix.hpp"
|
||||
#include "tut_assert.hpp"
|
||||
#include "tut_runner.hpp"
|
||||
|
||||
#if defined(TUT_USE_SEH)
|
||||
#include <windows.h>
|
||||
#include <winbase.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Template Unit Tests Framework for C++.
|
||||
* http://tut.dozen.ru
|
||||
*
|
||||
* @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
|
||||
*/
|
||||
namespace tut
|
||||
{
|
||||
|
||||
/**
|
||||
* Test object. Contains data test run upon and default test method
|
||||
* implementation. Inherited from Data to allow tests to
|
||||
* access test data as members.
|
||||
*/
|
||||
template <class Data>
|
||||
class test_object : public Data, public test_object_posix
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
test_object()
|
||||
{
|
||||
}
|
||||
|
||||
void set_test_name(const std::string& current_test_name)
|
||||
{
|
||||
current_test_name_ = current_test_name;
|
||||
}
|
||||
|
||||
const std::string& get_test_name() const
|
||||
{
|
||||
return current_test_name_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default do-nothing test.
|
||||
*/
|
||||
template <int n>
|
||||
void test()
|
||||
{
|
||||
called_method_was_a_dummy_test_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The flag is set to true by default (dummy) test.
|
||||
* Used to detect usused test numbers and avoid unnecessary
|
||||
* test object creation which may be time-consuming depending
|
||||
* on operations described in Data::Data() and Data::~Data().
|
||||
* TODO: replace with throwing special exception from default test.
|
||||
*/
|
||||
bool called_method_was_a_dummy_test_;
|
||||
|
||||
private:
|
||||
std::string current_test_name_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Walks through test tree and stores address of each
|
||||
* test method in group. Instantiation stops at 0.
|
||||
*/
|
||||
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&)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Test group; used to recreate test object instance for
|
||||
* each new test since we have to have reinitialized
|
||||
* Data base class.
|
||||
*/
|
||||
template <class Data, int MaxTestsInGroup = 50>
|
||||
class test_group : public group_base, public test_group_posix
|
||||
{
|
||||
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_;
|
||||
|
||||
/**
|
||||
* Exception-in-destructor-safe smart-pointer class.
|
||||
*/
|
||||
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_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell ptr it can throw from destructor. Right way is to
|
||||
* use std::uncaught_exception(), but some compilers lack
|
||||
* correct implementation of the function.
|
||||
*/
|
||||
void permit_throw()
|
||||
{
|
||||
permit_throw_in_dtor = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specially treats exceptions in test object destructor;
|
||||
* if test itself failed, exceptions in destructor
|
||||
* are ignored; if test was successful and destructor failed,
|
||||
* warning exception throwed.
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-init holder to get brand new object.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Creates and registers test group with specified name.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor is used in self-test run only.
|
||||
*/
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers test method under given number.
|
||||
*/
|
||||
void reg(int n, testmethod tm)
|
||||
{
|
||||
tests_[n] = tm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset test position before first test.
|
||||
*/
|
||||
void rewind()
|
||||
{
|
||||
current_test_ = tests_.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs next test.
|
||||
*/
|
||||
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
|
||||
{
|
||||
tests_iterator current_test = current_test_++;
|
||||
|
||||
test_result tr = run_test_(current_test, obj);
|
||||
|
||||
return tr;
|
||||
}
|
||||
catch (const no_such_test&)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw no_more_tests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one test by position.
|
||||
*/
|
||||
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;
|
||||
test_result tr = run_test_(ti, obj);
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* VC allows only one exception handling type per function,
|
||||
* so I have to split the method.
|
||||
*/
|
||||
test_result run_test_(const tests_iterator& ti, safe_holder<object>& obj)
|
||||
{
|
||||
std::string current_test_name;
|
||||
|
||||
test_result tr(name_, ti->first, current_test_name, test_result::ok);
|
||||
|
||||
try
|
||||
{
|
||||
if (run_test_seh_(ti->second, obj, current_test_name) == false)
|
||||
{
|
||||
throw seh("seh");
|
||||
}
|
||||
}
|
||||
catch (const no_such_test&)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (const rethrown& ex)
|
||||
{
|
||||
tr = ex.tr;
|
||||
tr.result = test_result::rethrown;
|
||||
}
|
||||
catch (const tut_error& ex)
|
||||
{
|
||||
tr.result = ex.result();
|
||||
tr.exception_typeid = typeid(ex).name();
|
||||
tr.message = ex.what();
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
tr.result = test_result::ex;
|
||||
tr.exception_typeid = typeid(ex).name();
|
||||
tr.message = ex.what();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// test failed with unknown exception
|
||||
tr.result = test_result::ex;
|
||||
}
|
||||
|
||||
if (obj.get())
|
||||
{
|
||||
tr.name = obj->get_test_name();
|
||||
|
||||
// try to report to parent, if exists
|
||||
send_result_(obj.get(), tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr.name = current_test_name;
|
||||
}
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one under SEH if platform supports it.
|
||||
*/
|
||||
bool run_test_seh_(testmethod tm, safe_holder<object>& obj,
|
||||
std::string& current_test_name)
|
||||
{
|
||||
#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");
|
||||
current_test_name = obj->get_test_name();
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (obj->called_method_was_a_dummy_test_)
|
||||
{
|
||||
// do not call obj.release(); reuse object
|
||||
throw no_such_test();
|
||||
}
|
||||
|
||||
current_test_name = obj->get_test_name();
|
||||
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)
|
||||
/**
|
||||
* Decides should we execute handler or ignore SE.
|
||||
*/
|
||||
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
|
||||
|
||||
176
libraries/include/tut/tut_assert.hpp
Normal file
176
libraries/include/tut/tut_assert.hpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#ifndef TUT_ASSERT_H_GUARD
|
||||
#define TUT_ASSERT_H_GUARD
|
||||
|
||||
#include "tut_exception.hpp"
|
||||
|
||||
#if defined(TUT_USE_POSIX)
|
||||
#include <errno.h>
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/**
|
||||
* Tests provided condition.
|
||||
* Throws if false.
|
||||
*/
|
||||
void ensure(bool cond)
|
||||
{
|
||||
if (!cond)
|
||||
{
|
||||
// TODO: default ctor?
|
||||
throw failure("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests provided condition.
|
||||
* Throws if true.
|
||||
*/
|
||||
void ensure_not(bool cond)
|
||||
{
|
||||
ensure(!cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests provided condition.
|
||||
* Throws if false.
|
||||
*/
|
||||
template <typename T>
|
||||
void ensure(const T msg, bool cond)
|
||||
{
|
||||
if (!cond)
|
||||
{
|
||||
throw failure(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests provided condition.
|
||||
* Throws if true.
|
||||
*/
|
||||
template <typename T>
|
||||
void ensure_not(const T msg, bool cond)
|
||||
{
|
||||
ensure(msg, !cond);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests two objects for being equal.
|
||||
* Throws if false.
|
||||
*
|
||||
* NB: both T and Q must have operator << defined somewhere, or
|
||||
* client code will not compile at all!
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests two objects for being at most in given distance one from another.
|
||||
* Borders are excluded.
|
||||
* Throws if false.
|
||||
*
|
||||
* NB: T must have operator << defined somewhere, or
|
||||
* client code will not compile at all! Also, T shall have
|
||||
* operators + and -, and be comparable.
|
||||
*/
|
||||
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 ensure_errno(const char *msg, bool cond)
|
||||
{
|
||||
if(!cond)
|
||||
{
|
||||
#if defined(TUT_USE_POSIX)
|
||||
char e[512];
|
||||
std::stringstream ss;
|
||||
ss << (msg ? msg : "")
|
||||
<< (msg? ": " : "")
|
||||
<< strerror_r(errno, e, sizeof(e));
|
||||
throw failure(ss.str().c_str());
|
||||
#else
|
||||
throw failure(msg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unconditionally fails with message.
|
||||
*/
|
||||
void fail(const char* msg = "")
|
||||
{
|
||||
throw failure(msg);
|
||||
}
|
||||
|
||||
void fail(const std::string &msg)
|
||||
{
|
||||
throw failure(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip test because of known failure.
|
||||
*/
|
||||
void skip(const char* msg = "")
|
||||
{
|
||||
throw skip_failure(msg);
|
||||
}
|
||||
|
||||
void skip(const std::string& msg)
|
||||
{
|
||||
throw skip_failure(msg);
|
||||
}
|
||||
|
||||
} // end of namespace
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
226
libraries/include/tut/tut_exception.hpp
Normal file
226
libraries/include/tut/tut_exception.hpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#ifndef TUT_EXCEPTION_H_GUARD
|
||||
#define TUT_EXCEPTION_H_GUARD
|
||||
|
||||
#include <stdexcept>
|
||||
#include "tut_result.hpp"
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
/**
|
||||
* The base for all TUT exceptions.
|
||||
*/
|
||||
struct tut_error : public std::exception
|
||||
{
|
||||
tut_error(const std::string& msg)
|
||||
: err_msg(msg)
|
||||
{
|
||||
}
|
||||
|
||||
virtual test_result::result_type result() const
|
||||
{
|
||||
return test_result::ex;
|
||||
}
|
||||
|
||||
const char* what() const throw()
|
||||
{
|
||||
return err_msg.c_str();
|
||||
}
|
||||
|
||||
~tut_error() throw()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
std::string err_msg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to be throwed when attempted to execute
|
||||
* missed test by number.
|
||||
*/
|
||||
struct no_such_test : public tut_error
|
||||
{
|
||||
no_such_test()
|
||||
: tut_error("no such test")
|
||||
{
|
||||
}
|
||||
|
||||
~no_such_test() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* No such test and passed test number is higher than
|
||||
* any test number in current group. Used in one-by-one
|
||||
* test running when upper bound is not known.
|
||||
*/
|
||||
struct beyond_last_test : public no_such_test
|
||||
{
|
||||
beyond_last_test()
|
||||
{
|
||||
}
|
||||
|
||||
~beyond_last_test() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Group not found exception.
|
||||
*/
|
||||
struct no_such_group : public tut_error
|
||||
{
|
||||
no_such_group(const std::string& grp)
|
||||
: tut_error(grp)
|
||||
{
|
||||
}
|
||||
|
||||
~no_such_group() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal exception to be throwed when
|
||||
* no more tests left in group or journal.
|
||||
*/
|
||||
struct no_more_tests
|
||||
{
|
||||
no_more_tests()
|
||||
{
|
||||
}
|
||||
|
||||
~no_more_tests() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal exception to be throwed when
|
||||
* test constructor has failed.
|
||||
*/
|
||||
struct bad_ctor : public tut_error
|
||||
{
|
||||
bad_ctor(const std::string& msg)
|
||||
: tut_error(msg)
|
||||
{
|
||||
}
|
||||
|
||||
test_result::result_type result() const
|
||||
{
|
||||
return test_result::ex_ctor;
|
||||
}
|
||||
|
||||
~bad_ctor() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to be throwed when ensure() fails or fail() called.
|
||||
*/
|
||||
struct failure : public tut_error
|
||||
{
|
||||
failure(const std::string& msg)
|
||||
: tut_error(msg)
|
||||
{
|
||||
}
|
||||
|
||||
test_result::result_type result() const
|
||||
{
|
||||
return test_result::fail;
|
||||
}
|
||||
|
||||
~failure() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to be throwed when test desctructor throwed an exception.
|
||||
*/
|
||||
struct warning : public tut_error
|
||||
{
|
||||
warning(const std::string& msg)
|
||||
: tut_error(msg)
|
||||
{
|
||||
}
|
||||
|
||||
test_result::result_type result() const
|
||||
{
|
||||
return test_result::warn;
|
||||
}
|
||||
|
||||
~warning() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to be throwed when test issued SEH (Win32)
|
||||
*/
|
||||
struct seh : public tut_error
|
||||
{
|
||||
seh(const std::string& msg)
|
||||
: tut_error(msg)
|
||||
{
|
||||
}
|
||||
|
||||
virtual test_result::result_type result() const
|
||||
{
|
||||
return test_result::term;
|
||||
}
|
||||
|
||||
~seh() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to be throwed when child processes fail.
|
||||
*/
|
||||
struct rethrown : public failure
|
||||
{
|
||||
explicit rethrown(const test_result &result)
|
||||
: failure(result.message), tr(result)
|
||||
{
|
||||
}
|
||||
|
||||
virtual test_result::result_type result() const
|
||||
{
|
||||
return test_result::rethrown;
|
||||
}
|
||||
|
||||
~rethrown() throw()
|
||||
{
|
||||
}
|
||||
|
||||
const test_result tr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception to be throwed when skip_fail() is called.
|
||||
*/
|
||||
struct skip_failure : public failure
|
||||
{
|
||||
skip_failure(const std::string& msg)
|
||||
: failure(msg)
|
||||
{
|
||||
}
|
||||
|
||||
test_result::result_type result() const
|
||||
{
|
||||
return test_result::skip;
|
||||
}
|
||||
|
||||
~skip_failure() throw()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
450
libraries/include/tut/tut_posix.hpp
Normal file
450
libraries/include/tut/tut_posix.hpp
Normal file
@@ -0,0 +1,450 @@
|
||||
#ifndef TUT_FORK_H_GUARD
|
||||
#define TUT_FORK_H_GUARD
|
||||
|
||||
#if defined(TUT_USE_POSIX)
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
|
||||
#include "tut_result.hpp"
|
||||
#include "tut_assert.hpp"
|
||||
#include "tut_runner.hpp"
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
template<typename, int>
|
||||
class test_group;
|
||||
|
||||
template<typename T>
|
||||
class test_object;
|
||||
|
||||
class test_group_posix
|
||||
{
|
||||
private:
|
||||
template<typename, int>
|
||||
friend class test_group;
|
||||
|
||||
template<typename T>
|
||||
void send_result_(const T *obj, const test_result &tr)
|
||||
{
|
||||
if(obj->get_pipe_() == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(tr.result != test_result::ok)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << int(tr.result) << "\n"
|
||||
<< tr.group << "\n"
|
||||
<< tr.test << "\n"
|
||||
<< tr.name << "\n"
|
||||
<< tr.exception_typeid << "\n";
|
||||
std::copy( tr.message.begin(), tr.message.end(), std::ostreambuf_iterator<char>(ss.rdbuf()) );
|
||||
|
||||
int size = ss.str().length();
|
||||
int w = write(obj->get_pipe_(), ss.str().c_str(), size);
|
||||
ensure_errno("write() failed", w == size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct tut_posix
|
||||
{
|
||||
pid_t fork()
|
||||
{
|
||||
test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
|
||||
ensure("trying to call 'fork' in ctor of test object", self != NULL);
|
||||
|
||||
return self->fork_();
|
||||
}
|
||||
|
||||
void ensure_child_exit(pid_t pid, int exit_status = 0)
|
||||
{
|
||||
test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
|
||||
ensure("trying to call 'ensure_child_exit' in ctor of test object", self != NULL);
|
||||
|
||||
int status;
|
||||
self->waitpid_(pid, &status);
|
||||
|
||||
self->ensure_child_exit_(status, exit_status);
|
||||
}
|
||||
|
||||
|
||||
void ensure_child_signal(pid_t pid, int signal = SIGTERM)
|
||||
{
|
||||
test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
|
||||
ensure("trying to call 'ensure_child_signal' in ctor of test object", self != NULL);
|
||||
|
||||
int status;
|
||||
self->waitpid_(pid, &status);
|
||||
|
||||
self->ensure_child_signal_(status, signal);
|
||||
}
|
||||
|
||||
std::set<pid_t> get_pids() const
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
const test_object<T> *self = dynamic_cast< const tut::test_object<T>* >(this);
|
||||
ensure("trying to call 'get_pids' in ctor of test object", self != NULL);
|
||||
|
||||
return self->get_pids_();
|
||||
}
|
||||
|
||||
virtual ~tut_posix()
|
||||
{
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class test_object_posix
|
||||
{
|
||||
public:
|
||||
typedef std::map<pid_t, int> pid_map;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
test_object_posix()
|
||||
: pipe_(-1)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
virtual ~test_object_posix()
|
||||
{
|
||||
// we have forked
|
||||
if(pipe_ != -1)
|
||||
{
|
||||
// in child, force exit
|
||||
std::exit(0);
|
||||
}
|
||||
|
||||
if(!pids_.empty())
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
// in parent, reap children
|
||||
for(std::map<pid_t, int>::iterator i = pids_.begin(); i != pids_.end(); ++i)
|
||||
{
|
||||
try {
|
||||
kill_child_(i->first);
|
||||
} catch(const rethrown &ex) {
|
||||
ss << std::endl << "child " << ex.tr.pid << " has thrown an exception: " << ex.what();
|
||||
} catch(const failure &ex) {
|
||||
ss << std::endl << ex.what();
|
||||
}
|
||||
}
|
||||
|
||||
if(!ss.str().empty())
|
||||
{
|
||||
fail(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
friend class tut_posix;
|
||||
|
||||
friend class test_group_posix;
|
||||
|
||||
int get_pipe_() const
|
||||
{
|
||||
return pipe_;
|
||||
}
|
||||
|
||||
|
||||
pid_t fork_()
|
||||
{
|
||||
// create pipe
|
||||
int fds[2];
|
||||
ensure_errno("pipe() failed", ::pipe(fds) == 0);
|
||||
|
||||
pid_t pid = ::fork();
|
||||
|
||||
ensure_errno("fork() failed", pid >= 0);
|
||||
|
||||
if(pid != 0)
|
||||
{
|
||||
// in parent, register pid
|
||||
ensure("duplicated child", pids_.insert( std::make_pair(pid, fds[0]) ).second);
|
||||
|
||||
// close writing side
|
||||
close(fds[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// in child, shutdown reporter
|
||||
tut::runner.get().set_callback(NULL);
|
||||
|
||||
// close reading side
|
||||
close(fds[0]);
|
||||
pipe_ = fds[1];
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
void kill_child_(pid_t pid)
|
||||
{
|
||||
int status;
|
||||
|
||||
if(waitpid_(pid, &status, WNOHANG) == pid)
|
||||
{
|
||||
ensure_child_exit_(status, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if(::kill(pid, SIGTERM) != 0)
|
||||
{
|
||||
if(errno == ESRCH)
|
||||
{
|
||||
// no such process
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// cannot kill, we are in trouble
|
||||
std::stringstream ss;
|
||||
char e[1024];
|
||||
ss << "child " << pid << " could not be killed with SIGTERM, " << strerror_r(errno, e, sizeof(e)) << std::endl;
|
||||
fail(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
if(waitpid_(pid, &status, WNOHANG) == pid)
|
||||
{
|
||||
// child killed, check signal
|
||||
ensure_child_signal_(status, SIGTERM);
|
||||
|
||||
ensure_equals("child process exists after SIGTERM", ::kill(pid, 0), -1);
|
||||
return;
|
||||
}
|
||||
|
||||
// child seems to be still exiting, give it some time
|
||||
sleep(2);
|
||||
|
||||
if(waitpid_(pid, &status, WNOHANG) != pid)
|
||||
{
|
||||
// child is still running, kill it
|
||||
if(::kill(pid, SIGKILL) != 0)
|
||||
{
|
||||
if(errno == ESRCH)
|
||||
{
|
||||
// no such process
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream ss;
|
||||
char e[1024];
|
||||
ss << "child " << pid << " could not be killed with SIGKILL, " << strerror_r(errno, e, sizeof(e)) << std::endl;
|
||||
fail(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
ensure_equals("wait after SIGKILL", waitpid_(pid, &status), pid);
|
||||
ensure_child_signal_(status, SIGKILL);
|
||||
|
||||
ensure_equals("child process exists after SIGKILL", ::kill(pid, 0), -1);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "child " << pid << " had to be killed with SIGKILL";
|
||||
fail(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
test_result receive_result_(std::istream &ss, pid_t pid)
|
||||
{
|
||||
test_result tr;
|
||||
|
||||
int type;
|
||||
ss >> type;
|
||||
tr.result = test_result::result_type(type);
|
||||
ss.ignore(1024, '\n');
|
||||
|
||||
std::getline(ss, tr.group);
|
||||
ss >> tr.test;
|
||||
ss.ignore(1024, '\n');
|
||||
std::getline(ss, tr.name);
|
||||
std::getline(ss, tr.exception_typeid);
|
||||
std::copy( std::istreambuf_iterator<char>(ss.rdbuf()),
|
||||
std::istreambuf_iterator<char>(),
|
||||
std::back_inserter(tr.message) );
|
||||
|
||||
tr.pid = pid;
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
pid_t waitpid_(pid_t pid, int *status, int flags = 0)
|
||||
{
|
||||
ensure("trying to wait for unknown pid", pids_.count(pid) > 0);
|
||||
|
||||
pid_t p = ::waitpid(pid, status, flags);
|
||||
if( (flags & WNOHANG) && (p != pid) )
|
||||
{
|
||||
return p;
|
||||
}
|
||||
|
||||
// read child result from pipe
|
||||
fd_set fdset;
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
FD_ZERO(&fdset);
|
||||
|
||||
int pipe = pids_[pid];
|
||||
FD_SET(pipe, &fdset);
|
||||
|
||||
int result = select(pipe+1, &fdset, NULL, NULL, &tv);
|
||||
ensure_errno("sanity check on select() failed", result >= 0);
|
||||
|
||||
if(result > 0)
|
||||
{
|
||||
ensure("sanity check on FD_ISSET() failed", FD_ISSET(pipe, &fdset) );
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
//TODO: max failure length
|
||||
char buffer[1024];
|
||||
int r = read(pipe, buffer, sizeof(buffer));
|
||||
ensure_errno("sanity check on read() failed", r >= 0);
|
||||
|
||||
if(r > 0)
|
||||
{
|
||||
ss.write(buffer, r);
|
||||
throw rethrown( receive_result_(ss, pid) );
|
||||
}
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
void ensure_child_exit_(int status, int exit_status)
|
||||
{
|
||||
if(WIFSIGNALED(status))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "child killed by signal " << WTERMSIG(status)
|
||||
<< ": expected exit with code " << exit_status;
|
||||
|
||||
throw failure(ss.str().c_str());
|
||||
}
|
||||
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
if(WEXITSTATUS(status) != exit_status)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "child exited, expected '"
|
||||
<< exit_status
|
||||
<< "' actual '"
|
||||
<< WEXITSTATUS(status)
|
||||
<< '\'';
|
||||
|
||||
throw failure(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if(WIFSTOPPED(status))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "child stopped by signal " << WTERMSIG(status)
|
||||
<< ": expected exit with code " << exit_status;
|
||||
throw failure(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_child_signal_(int status, int signal)
|
||||
{
|
||||
if(WIFSIGNALED(status))
|
||||
{
|
||||
if(WTERMSIG(status) != signal)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "child killed by signal, expected '"
|
||||
<< signal
|
||||
<< "' actual '"
|
||||
<< WTERMSIG(status)
|
||||
<< '\'';
|
||||
throw failure(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if(WIFEXITED(status))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "child exited with code " << WEXITSTATUS(status)
|
||||
<< ": expected signal " << signal;
|
||||
|
||||
throw failure(ss.str().c_str());
|
||||
}
|
||||
|
||||
if(WIFSTOPPED(status))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "child stopped by signal " << WTERMSIG(status)
|
||||
<< ": expected kill by signal " << signal;
|
||||
|
||||
throw failure(ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::set<pid_t> get_pids_() const
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
set<pid_t> pids;
|
||||
for(pid_map::const_iterator i = pids_.begin(); i != pids_.end(); ++i)
|
||||
{
|
||||
pids.insert( i->first );
|
||||
}
|
||||
|
||||
return pids;
|
||||
}
|
||||
|
||||
pid_map pids_;
|
||||
int pipe_;
|
||||
};
|
||||
|
||||
} // namespace tut
|
||||
|
||||
#else
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
struct test_object_posix
|
||||
{
|
||||
};
|
||||
|
||||
struct test_group_posix
|
||||
{
|
||||
template<typename T>
|
||||
void send_result_(const T*, const test_result &)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tut
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
243
libraries/include/tut/tut_reporter.hpp
Normal file
243
libraries/include/tut/tut_reporter.hpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#ifndef TUT_REPORTER
|
||||
#define TUT_REPORTER
|
||||
|
||||
#include <tut/tut.hpp>
|
||||
|
||||
/**
|
||||
* Template Unit Tests Framework for C++.
|
||||
* http://tut.dozen.ru
|
||||
*
|
||||
* @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com
|
||||
*/
|
||||
namespace
|
||||
{
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const tut::test_result& tr)
|
||||
{
|
||||
switch(tr.result)
|
||||
{
|
||||
case tut::test_result::ok:
|
||||
os << '.';
|
||||
break;
|
||||
case tut::test_result::fail:
|
||||
os << '[' << tr.test << "=F]";
|
||||
break;
|
||||
case tut::test_result::ex_ctor:
|
||||
os << '[' << tr.test << "=C]";
|
||||
break;
|
||||
case tut::test_result::ex:
|
||||
os << '[' << tr.test << "=X]";
|
||||
break;
|
||||
case tut::test_result::warn:
|
||||
os << '[' << tr.test << "=W]";
|
||||
break;
|
||||
case tut::test_result::term:
|
||||
os << '[' << tr.test << "=T]";
|
||||
break;
|
||||
case tut::test_result::rethrown:
|
||||
os << '[' << tr.test << "=P]";
|
||||
break;
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
} // end of namespace
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
/**
|
||||
* Default TUT callback handler.
|
||||
*/
|
||||
class reporter : public tut::callback
|
||||
{
|
||||
std::string current_group;
|
||||
typedef std::vector<tut::test_result> not_passed_list;
|
||||
not_passed_list not_passed;
|
||||
std::ostream& os;
|
||||
|
||||
public:
|
||||
|
||||
int ok_count;
|
||||
int exceptions_count;
|
||||
int failures_count;
|
||||
int terminations_count;
|
||||
int warnings_count;
|
||||
|
||||
reporter()
|
||||
: os(std::cout)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
reporter(std::ostream& out)
|
||||
: os(out)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void run_started()
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
void test_completed(const tut::test_result& tr)
|
||||
{
|
||||
if (tr.group != current_group)
|
||||
{
|
||||
os << std::endl << tr.group << ": " << std::flush;
|
||||
current_group = tr.group;
|
||||
}
|
||||
|
||||
os << tr << std::flush;
|
||||
if (tr.result == tut::test_result::ok)
|
||||
{
|
||||
ok_count++;
|
||||
}
|
||||
else if (tr.result == tut::test_result::ex)
|
||||
{
|
||||
exceptions_count++;
|
||||
}
|
||||
else if (tr.result == tut::test_result::ex_ctor)
|
||||
{
|
||||
exceptions_count++;
|
||||
}
|
||||
else if (tr.result == tut::test_result::fail)
|
||||
{
|
||||
failures_count++;
|
||||
}
|
||||
else if (tr.result == tut::test_result::rethrown)
|
||||
{
|
||||
failures_count++;
|
||||
}
|
||||
else if (tr.result == tut::test_result::warn)
|
||||
{
|
||||
warnings_count++;
|
||||
}
|
||||
else
|
||||
{
|
||||
terminations_count++;
|
||||
}
|
||||
|
||||
if (tr.result != tut::test_result::ok)
|
||||
{
|
||||
not_passed.push_back(tr);
|
||||
}
|
||||
}
|
||||
|
||||
void run_completed()
|
||||
{
|
||||
os << std::endl;
|
||||
|
||||
if (not_passed.size() > 0)
|
||||
{
|
||||
not_passed_list::const_iterator i = not_passed.begin();
|
||||
while (i != not_passed.end())
|
||||
{
|
||||
tut::test_result tr = *i;
|
||||
|
||||
os << std::endl;
|
||||
|
||||
os << "---> " << "group: " << tr.group
|
||||
<< ", test: test<" << tr.test << ">"
|
||||
<< (!tr.name.empty() ? (std::string(" : ") + tr.name) : std::string())
|
||||
<< std::endl;
|
||||
|
||||
#if defined(TUT_USE_POSIX)
|
||||
if(tr.pid != getpid())
|
||||
{
|
||||
os << " child pid: " << tr.pid << std::endl;
|
||||
}
|
||||
#endif
|
||||
os << " problem: ";
|
||||
switch(tr.result)
|
||||
{
|
||||
case test_result::rethrown:
|
||||
os << "assertion failed in child" << std::endl;
|
||||
break;
|
||||
case test_result::fail:
|
||||
os << "assertion failed" << std::endl;
|
||||
break;
|
||||
case test_result::ex:
|
||||
case test_result::ex_ctor:
|
||||
os << "unexpected exception" << std::endl;
|
||||
if( tr.exception_typeid != "" )
|
||||
{
|
||||
os << " exception typeid: "
|
||||
<< tr.exception_typeid << std::endl;
|
||||
}
|
||||
break;
|
||||
case test_result::term:
|
||||
os << "would be terminated" << std::endl;
|
||||
break;
|
||||
case test_result::warn:
|
||||
os << "test passed, but cleanup code (destructor) raised"
|
||||
" an exception" << std::endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tr.message.empty())
|
||||
{
|
||||
if (tr.result == test_result::fail)
|
||||
{
|
||||
os << " failed assertion: \"" << tr.message << "\""
|
||||
<< std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
os << " message: \"" << tr.message << "\""
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
os << std::endl;
|
||||
|
||||
os << "tests summary:";
|
||||
if (terminations_count > 0)
|
||||
{
|
||||
os << " terminations:" << terminations_count;
|
||||
}
|
||||
if (exceptions_count > 0)
|
||||
{
|
||||
os << " exceptions:" << exceptions_count;
|
||||
}
|
||||
if (failures_count > 0)
|
||||
{
|
||||
os << " failures:" << failures_count;
|
||||
}
|
||||
if (warnings_count > 0)
|
||||
{
|
||||
os << " warnings:" << warnings_count;
|
||||
}
|
||||
os << " ok:" << ok_count;
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
bool all_ok() const
|
||||
{
|
||||
return not_passed.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void init()
|
||||
{
|
||||
ok_count = 0;
|
||||
exceptions_count = 0;
|
||||
failures_count = 0;
|
||||
terminations_count = 0;
|
||||
warnings_count = 0;
|
||||
not_passed.clear();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
394
libraries/include/tut/tut_restartable.hpp
Normal file
394
libraries/include/tut/tut_restartable.hpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#ifndef TUT_RESTARTABLE_H_GUARD
|
||||
#define TUT_RESTARTABLE_H_GUARD
|
||||
|
||||
#include <tut/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
|
||||
|
||||
130
libraries/include/tut/tut_result.hpp
Normal file
130
libraries/include/tut/tut_result.hpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#ifndef TUT_RESULT_H_GUARD
|
||||
#define TUT_RESULT_H_GUARD
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
#if defined(TUT_USE_POSIX)
|
||||
struct test_result_posix
|
||||
{
|
||||
test_result_posix()
|
||||
: pid(getpid())
|
||||
{
|
||||
}
|
||||
|
||||
pid_t pid;
|
||||
};
|
||||
#else
|
||||
struct test_result_posix
|
||||
{
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Return type of runned test/test group.
|
||||
*
|
||||
* For test: contains result of test and, possible, message
|
||||
* for failure or exception.
|
||||
*/
|
||||
struct test_result : public test_result_posix
|
||||
{
|
||||
/**
|
||||
* Test group name.
|
||||
*/
|
||||
std::string group;
|
||||
|
||||
/**
|
||||
* Test number in group.
|
||||
*/
|
||||
int test;
|
||||
|
||||
/**
|
||||
* Test name (optional)
|
||||
*/
|
||||
std::string name;
|
||||
|
||||
/**
|
||||
* ok - test finished successfully
|
||||
* fail - test failed with ensure() or fail() methods
|
||||
* ex - test throwed an exceptions
|
||||
* warn - test finished successfully, but test destructor throwed
|
||||
* term - test forced test application to terminate abnormally
|
||||
* skip - test skpped because it is a known failure case
|
||||
*/
|
||||
enum result_type
|
||||
{
|
||||
ok,
|
||||
fail,
|
||||
ex,
|
||||
warn,
|
||||
term,
|
||||
ex_ctor,
|
||||
rethrown,
|
||||
skip,
|
||||
};
|
||||
|
||||
result_type result;
|
||||
|
||||
/**
|
||||
* Exception message for failed test.
|
||||
*/
|
||||
std::string message;
|
||||
std::string exception_typeid;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
test_result()
|
||||
: test(0),
|
||||
result(ok)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
test_result(const std::string& grp, int pos,
|
||||
const std::string& test_name, result_type res)
|
||||
: group(grp),
|
||||
test(pos),
|
||||
name(test_name),
|
||||
result(res)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with exception.
|
||||
*/
|
||||
test_result(const std::string& grp,int pos,
|
||||
const std::string& test_name, result_type res,
|
||||
const std::exception& ex)
|
||||
: group(grp),
|
||||
test(pos),
|
||||
name(test_name),
|
||||
result(res),
|
||||
message(ex.what()),
|
||||
exception_typeid(typeid(ex).name())
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor with typeid.
|
||||
*/
|
||||
test_result(const std::string& grp,int pos,
|
||||
const std::string& test_name, result_type res,
|
||||
const std::string& ex_typeid,
|
||||
const std::string& msg)
|
||||
: group(grp),
|
||||
test(pos),
|
||||
name(test_name),
|
||||
result(res),
|
||||
message(msg),
|
||||
exception_typeid(ex_typeid)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
302
libraries/include/tut/tut_runner.hpp
Normal file
302
libraries/include/tut/tut_runner.hpp
Normal file
@@ -0,0 +1,302 @@
|
||||
#ifndef TUT_RUNNER_H_GUARD
|
||||
#define TUT_RUNNER_H_GUARD
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "tut_exception.hpp"
|
||||
|
||||
namespace tut
|
||||
{
|
||||
|
||||
/**
|
||||
* Interface.
|
||||
* Test group operations.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Test runner callback interface.
|
||||
* Can be implemented by caller to update
|
||||
* tests results in real-time. User can implement
|
||||
* any of callback methods, and leave unused
|
||||
* in default implementation.
|
||||
*/
|
||||
struct callback
|
||||
{
|
||||
/**
|
||||
* Virtual destructor is a must for subclassed types.
|
||||
*/
|
||||
virtual ~callback()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when new test run started.
|
||||
*/
|
||||
virtual void run_started()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a group started
|
||||
* @param name Name of the group
|
||||
*/
|
||||
virtual void group_started(const std::string& /*name*/)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a test finished.
|
||||
* @param tr Test results.
|
||||
*/
|
||||
virtual void test_completed(const test_result& /*tr*/)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a group is completed
|
||||
* @param name Name of the group
|
||||
*/
|
||||
virtual void group_completed(const std::string& /*name*/)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when all tests in run completed.
|
||||
*/
|
||||
virtual void run_completed()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Typedef for runner::list_groups()
|
||||
*/
|
||||
typedef std::vector<std::string> groupnames;
|
||||
|
||||
/**
|
||||
* Test runner.
|
||||
*/
|
||||
class test_runner
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
test_runner()
|
||||
: callback_(&default_callback_)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores another group for getting by name.
|
||||
*/
|
||||
void register_group(const std::string& name, group_base* gr)
|
||||
{
|
||||
if (gr == 0)
|
||||
{
|
||||
throw tut_error("group shall be non-null");
|
||||
}
|
||||
|
||||
if (groups_.find(name) != groups_.end())
|
||||
{
|
||||
std::string msg("attempt to add already existent group " + name);
|
||||
// this exception terminates application so we use cerr also
|
||||
// TODO: should this message appear in stream?
|
||||
std::cerr << msg << std::endl;
|
||||
throw tut_error(msg);
|
||||
}
|
||||
|
||||
groups_.insert( std::make_pair(name, gr) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores callback object.
|
||||
*/
|
||||
void set_callback(callback* cb)
|
||||
{
|
||||
callback_ = cb == 0 ? &default_callback_ : cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns callback object.
|
||||
*/
|
||||
callback& get_callback() const
|
||||
{
|
||||
return *callback_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of known test groups.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all tests in all groups.
|
||||
* @param callback Callback object if exists; null otherwise
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all tests in specified group.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs one test in specified group.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_;
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton for test_runner implementation.
|
||||
* Instance with name runner_singleton shall be implemented
|
||||
* by user.
|
||||
*/
|
||||
class test_runner_singleton
|
||||
{
|
||||
public:
|
||||
|
||||
static test_runner& get()
|
||||
{
|
||||
static test_runner tr;
|
||||
return tr;
|
||||
}
|
||||
};
|
||||
|
||||
extern test_runner_singleton runner;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user