#ifndef TUT_H_GUARD #define TUT_H_GUARD #include #include #include #include #include #include #include #include #include #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 #include #endif /** * Template Unit Tests Framework for C++. * http://tut.dozen.ru * * @author Vladimir Dyuzhev, Vladimir.Dyuzhev@gmail.com */ namespace tut { template class test_group; /** * 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 test_object : public Data, public test_object_posix { template friend class test_group; void set_test_group(const char *group) { current_test_group_ = group; } void set_test_id(int current_test_id) { current_test_id_ = current_test_id; } public: /** * Default constructor */ test_object() : called_method_was_a_dummy_test_(false), current_test_id_(0), current_test_name_(), current_test_group_() { } 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_; } const std::string& get_test_group() const { return current_test_group_; } int get_test_id() const { return current_test_id_; } /** * Default do-nothing test. */ template 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(). */ bool called_method_was_a_dummy_test_; virtual ~test_object() { } private: int current_test_id_; std::string current_test_name_; std::string current_test_group_; }; /** * Walks through test tree and stores address of each * test method in group. Instantiation stops at 0. */ template struct tests_registerer { static void reg(Group& group) { group.reg(n, &Test::template test); tests_registerer::reg(group); } }; template struct tests_registerer { 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 test_group : public group_base, public test_group_posix { test_group(const test_group&); void operator=(const test_group&); const char* name_; typedef void (test_object::*testmethod)(); typedef std::map 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_; enum seh_result { SEH_OK, #if defined(TUT_USE_SEH) SEH_CTOR, SEH_TEST, #endif SEH_DUMMY }; /** * Exception-in-destructor-safe smart-pointer class. */ template 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 defined(TUT_USE_SEH) if (delete_obj() == false) { throw warning("destructor of test object raised" " an SEH exception"); } #else bool d = delete_obj(); assert(d && "delete failed with SEH disabled: runtime bug?"); #endif } 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 object; /** * Creates and registers test group with specified name. */ test_group(const char* name) : name_(name), tests_(), current_test_() { // register itself runner.get().register_group(name_,this); // register all tests tests_registerer::reg(*this); } /** * This constructor is used in self-test run only. */ test_group(const char* name, test_runner& another_runner) : name_(name), tests_(), current_test_() { // register itself another_runner.register_group(name_, this); // register all tests tests_registerer, 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. */ bool run_next(test_result &tr) { if (current_test_ == tests_.end()) { return false; } // find next user-specialized test safe_holder obj; while (current_test_ != tests_.end()) { tests_iterator current_test = current_test_++; if(run_test_(current_test, obj, tr) && tr.result != test_result::dummy) { return true; } } return false; } /** * Runs one test by position. */ bool run_test(int n, test_result &tr) { if (tests_.rbegin() == tests_.rend() || tests_.rbegin()->first < n) { return false; } // withing scope; check if given test exists tests_iterator ti = tests_.find(n); if (ti == tests_.end()) { return false; } safe_holder obj; return run_test_(ti, obj, tr); } /** * VC allows only one exception handling type per function, * so I have to split the method. */ bool run_test_(const tests_iterator& ti, safe_holder& obj, test_result &tr) { std::string current_test_name; tr = test_result(name_, ti->first, current_test_name, test_result::ok); try { switch (run_test_seh_(ti->second, obj, current_test_name, ti->first)) { #if defined(TUT_USE_SEH) case SEH_CTOR: throw bad_ctor("seh"); break; case SEH_TEST: throw seh("seh"); break; #endif case SEH_DUMMY: tr.result = test_result::dummy; break; case SEH_OK: // ok break; } } catch (const rethrown& ex) { tr = ex.tr; tr.result = test_result::rethrown; } catch (const tut_error& ex) { tr.result = ex.result(); tr.exception_typeid = ex.type(); tr.message = ex.what(); } catch (const std::exception& ex) { tr.result = test_result::ex; tr.exception_typeid = type_name(ex); 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 true; } /** * Runs one under SEH if platform supports it. */ seh_result run_test_seh_(testmethod tm, safe_holder& obj, std::string& current_test_name, int current_test_id) { #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()->set_test_id(current_test_id); obj.get()->set_test_group(name_); (obj.get()->*tm)(); #if defined(TUT_USE_SEH) } __except(handle_seh_(::GetExceptionCode())) { current_test_name = obj->get_test_name(); return SEH_TEST; } #endif if (obj->called_method_was_a_dummy_test_) { // do not call obj.release(); reuse object return SEH_DUMMY; } current_test_name = obj->get_test_name(); obj.permit_throw(); obj.release(); #if defined(TUT_USE_SEH) } __except(handle_seh_(::GetExceptionCode())) { return SEH_CTOR; } #endif return SEH_OK; } void reset_holder_(safe_holder& 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 // TUT_H_GUARD