--- /dev/null
+
+#include <tut/tut.hpp>
--- /dev/null
+#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>
+
+#if defined(linux)
+#define TUT_USE_POSIX
+#endif
+
+#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_;
+ }
+
+ void set_test_id(int current_test_id)
+ {
+ current_test_id_ = current_test_id;
+ }
+
+ int get_test_id() const
+ {
+ return current_test_id_;
+ }
+
+ /**
+ * 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:
+ int current_test_id_;
+ 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_;
+
+ enum seh_result
+ {
+ SEH_OK,
+ SEH_CTOR,
+ SEH_TEST,
+ SEH_DUMMY
+ };
+
+ /**
+ * 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.
+ */
+ bool run_next(test_result &tr)
+ {
+ if (current_test_ == tests_.end())
+ {
+ return false;
+ }
+
+ // find next user-specialized test
+ safe_holder<object> 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<object> 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<object>& 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))
+ {
+ case SEH_CTOR:
+ throw bad_ctor("seh");
+ break;
+
+ case SEH_TEST:
+ throw seh("seh");
+ break;
+
+ 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 = 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 true;
+ }
+
+ /**
+ * Runs one under SEH if platform supports it.
+ */
+ seh_result run_test_seh_(testmethod tm, safe_holder<object>& 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()->*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<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
+
--- /dev/null
+#ifndef TUT_ASSERT_H_GUARD
+#define TUT_ASSERT_H_GUARD
+
+#include "tut_exception.hpp"
+#include <limits>
+#include <iomanip>
+
+#if defined(TUT_USE_POSIX)
+#include <errno.h>
+#include <cstring>
+#endif
+
+namespace tut
+{
+
+ namespace detail
+ {
+ template<typename M>
+ std::ostream &msg_prefix(std::ostream &str, const M &msg)
+ {
+ std::stringstream ss;
+ ss << msg;
+
+ if(!ss.str().empty())
+ {
+ str << ss.rdbuf() << ": ";
+ }
+
+ return str;
+ }
+ }
+
+
+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 M>
+void ensure(const M& msg, bool cond)
+{
+ if (!cond)
+ {
+ throw failure(msg);
+ }
+}
+
+/**
+ * Tests provided condition.
+ * Throws if true.
+ */
+template <typename M>
+void ensure_not(const M& 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 <typename M, typename LHS, typename RHS>
+void ensure_equals(const M& msg, const LHS& actual, const RHS& expected)
+{
+ if (expected != actual)
+ {
+ std::stringstream ss;
+ detail::msg_prefix(ss,msg)
+ << "expected '"
+ << expected
+ << "' actual '"
+ << actual
+ << '\'';
+ throw failure(ss.str());
+ }
+}
+
+template <typename LHS, typename RHS>
+void ensure_equals(const LHS& actual, const RHS& expected)
+{
+ ensure_equals("Values are not equal", actual, expected);
+}
+
+template<typename M>
+void ensure_equals(const M& msg, const double& actual, const double& expected,
+ const double& epsilon = std::numeric_limits<double>::epsilon())
+{
+ const double diff = actual - expected;
+
+ if ( !((diff <= epsilon) && (diff >= -epsilon )) )
+ {
+ std::stringstream ss;
+ detail::msg_prefix(ss,msg)
+ << std::scientific
+ << std::showpoint
+ << std::setprecision(16)
+ << "expected " << expected
+ << " actual " << actual
+ << " with precision " << epsilon;
+ throw failure(ss.str());
+ }
+}
+/**
+ * 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.
+ *
+ * TODO: domains are wrong, T - T might not yield T, but Q
+ */
+template <typename M, class T>
+void ensure_distance(const M& msg, const T& actual, const T& expected, const T& distance)
+{
+ if (expected-distance >= actual || expected+distance <= actual)
+ {
+ std::stringstream ss;
+ detail::msg_prefix(ss,msg)
+ << " expected ("
+ << expected-distance
+ << " - "
+ << expected+distance
+ << ") actual '"
+ << actual
+ << '\'';
+ throw failure(ss.str());
+ }
+}
+
+template <class T>
+void ensure_distance(const T& actual, const T& expected, const T& distance)
+{
+ ensure_distance<>("Distance is wrong", actual, expected, distance);
+}
+
+template<typename M>
+void ensure_errno(const M& msg, bool cond)
+{
+ if(!cond)
+ {
+#if defined(TUT_USE_POSIX)
+ char e[512];
+ std::stringstream ss;
+ detail::msg_prefix(ss,msg)
+ << strerror_r(errno, e, sizeof(e));
+ throw failure(ss.str());
+#else
+ throw failure(msg);
+#endif
+ }
+}
+
+/**
+ * Unconditionally fails with message.
+ */
+void fail(const char* msg = "")
+{
+ throw failure(msg);
+}
+
+template<typename M>
+void fail(const M& msg)
+{
+ throw failure(msg);
+}
+
+} // end of namespace
+
+}
+
+#endif
+
--- /dev/null
+#ifndef TUT_CONSOLE_REPORTER
+#define TUT_CONSOLE_REPORTER
+
+#include <tut/tut.hpp>
+#include <cassert>
+
+/**
+ * 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;
+ case tut::test_result::dummy:
+ assert(!"Should never be called");
+ }
+
+ return os;
+}
+
+} // end of namespace
+
+namespace tut
+{
+
+/**
+ * Default TUT callback handler.
+ */
+class console_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;
+
+ console_reporter()
+ : os(std::cout)
+ {
+ init();
+ }
+
+ console_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;
+
+ // update global statistics
+ switch (tr.result) {
+ case test_result::ok:
+ ok_count++;
+ break;
+ case test_result::fail:
+ case test_result::rethrown:
+ failures_count++;
+ break;
+ case test_result::ex:
+ case test_result::ex_ctor:
+ exceptions_count++;
+ break;
+ case test_result::warn:
+ warnings_count++;
+ break;
+ case test_result::term:
+ terminations_count++;
+ break;
+ case tut::test_result::dummy:
+ assert(!"Should never be called");
+ } // switch
+
+ 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
--- /dev/null
+\r
+#ifndef TUT_CPPUNIT_REPORTER\r
+#define TUT_CPPUNIT_REPORTER\r
+\r
+#include <tut/tut.hpp>\r
+#include <string>\r
+#include <fstream>\r
+#include <vector>\r
+#include <stdexcept>\r
+\r
+namespace tut\r
+{\r
+\r
+/**\r
+ * CppUnit TUT reporter\r
+ */\r
+class cppunit_reporter : public tut::callback\r
+{\r
+ private:\r
+ std::vector<tut::test_result> failed_tests;\r
+ std::vector<tut::test_result> passed_tests;\r
+ std::string filename;\r
+\r
+ std::string encode(const std::string & text)\r
+ {\r
+ std::string out;\r
+\r
+ for (unsigned int i=0; i<text.length(); ++i) {\r
+ char c = text[i];\r
+ switch (c) {\r
+ case '<':\r
+ out += "<";\r
+ break;\r
+ case '>':\r
+ out += ">";\r
+ break;\r
+ case '&':\r
+ out += "&";\r
+ break;\r
+ case '\'':\r
+ out += "'";\r
+ break;\r
+ case '"':\r
+ out += """;\r
+ break;\r
+ default:\r
+ out += c;\r
+ }\r
+ }\r
+\r
+ return out;\r
+ }\r
+\r
+public:\r
+\r
+ cppunit_reporter(const std::string & _filename = "")\r
+ {\r
+ setFilename(_filename);\r
+ }\r
+\r
+ void setFilename(const std::string & _filename)\r
+ {\r
+ if (_filename == "")\r
+ {\r
+ filename = "testResult.xml";\r
+ }\r
+ else\r
+ {\r
+ filename = _filename;\r
+ }\r
+ }\r
+\r
+ void run_started()\r
+ {\r
+ failed_tests.clear();\r
+ passed_tests.clear();\r
+ }\r
+\r
+ void test_completed(const tut::test_result& tr)\r
+ {\r
+ if (tr.result == test_result::ok) {\r
+ passed_tests.push_back(tr);\r
+ } else {\r
+ failed_tests.push_back(tr);\r
+ }\r
+ }\r
+\r
+ void run_completed()\r
+ {\r
+ int errors = 0;\r
+ int failures = 0;\r
+ std::string failure_type;\r
+ std::string failure_msg;\r
+ std::ofstream xmlfile;\r
+\r
+ xmlfile.open(filename.c_str(), std::ios::in | std::ios::trunc);\r
+ if (!xmlfile.is_open()) {\r
+ throw (std::runtime_error("Cannot open file for output"));\r
+ }\r
+\r
+ /* *********************** header ***************************** */\r
+ xmlfile << "<?xml version=\"1.0\" encoding='ISO-8859-1' standalone='yes' ?>" << std::endl\r
+ << "<TestRun>" << std::endl;\r
+\r
+ /* *********************** failed tests ***************************** */\r
+ if (failed_tests.size() > 0) {\r
+ xmlfile << " <FailedTests>" << std::endl;\r
+\r
+ for (unsigned int i=0; i<failed_tests.size(); i++) {\r
+ switch (failed_tests[i].result) {\r
+ case test_result::fail:\r
+ failure_type = "Assertion";\r
+ failure_msg = "";\r
+ failures++;\r
+ break;\r
+ case test_result::ex:\r
+ failure_type = "Assertion";\r
+ failure_msg = "Thrown exception: " + failed_tests[i].exception_typeid + '\n';\r
+ failures++;\r
+ break;\r
+ case test_result::warn:\r
+ failure_type = "Assertion";\r
+ failure_msg = "Destructor failed.\n";\r
+ failures++;\r
+ break;\r
+ case test_result::term:\r
+ failure_type = "Error";\r
+ failure_msg = "Test application terminated abnormally.\n";\r
+ errors++;\r
+ break;\r
+ case test_result::ex_ctor:\r
+ failure_type = "Error";\r
+ failure_msg = "Constructor has thrown an exception: " + failed_tests[i].exception_typeid + '\n';\r
+ errors++;\r
+ break;\r
+ case test_result::rethrown:\r
+ failure_type = "Assertion";\r
+ failure_msg = "Child failed";\r
+ failures++;\r
+ break;\r
+ default:\r
+ failure_type = "Error";\r
+ failure_msg = "Unknown test status, this should have never happened. "\r
+ "You may just have found a BUG in TUT CppUnit reporter, please report it immediately.\n";\r
+ errors++;\r
+ break;\r
+ }\r
+\r
+ xmlfile << " <FailedTest id=\"" << failed_tests[i].test << "\">" << std::endl\r
+ << " <Name>" << encode(failed_tests[i].group) + "::" + encode(failed_tests[i].name) << "</Name>" << std::endl\r
+ << " <FailureType>" << failure_type << "</FailureType>" << std::endl\r
+ << " <Location>" << std::endl\r
+ << " <File>Unknown</File>" << std::endl\r
+ << " <Line>Unknown</Line>" << std::endl\r
+ << " </Location>" << std::endl\r
+ << " <Message>" << encode(failure_msg + failed_tests[i].message) << "</Message>" << std::endl\r
+ << " </FailedTest>" << std::endl;\r
+ }\r
+\r
+ xmlfile << " </FailedTests>" << std::endl;\r
+ }\r
+\r
+ /* *********************** passed tests ***************************** */\r
+ if (passed_tests.size() > 0) {\r
+ xmlfile << " <SuccessfulTests>" << std::endl;\r
+\r
+ for (unsigned int i=0; i<passed_tests.size(); i++) {\r
+ xmlfile << " <Test id=\"" << passed_tests[i].test << "\">" << std::endl\r
+ << " <Name>" << encode(passed_tests[i].group) + "::" + encode(passed_tests[i].name) << "</Name>" << std::endl\r
+ << " </Test>" << std::endl;\r
+ }\r
+\r
+ xmlfile << " </SuccessfulTests>" << std::endl;\r
+ }\r
+\r
+ /* *********************** statistics ***************************** */\r
+ xmlfile << " <Statistics>" << std::endl\r
+ << " <Tests>" << (failed_tests.size() + passed_tests.size()) << "</Tests>" << std::endl\r
+ << " <FailuresTotal>" << failed_tests.size() << "</FailuresTotal>" << std::endl\r
+ << " <Errors>" << errors << "</Errors>" << std::endl\r
+ << " <Failures>" << failures << "</Failures>" << std::endl\r
+ << " </Statistics>" << std::endl;\r
+\r
+ /* *********************** footer ***************************** */\r
+ xmlfile << "</TestRun>" << std::endl;\r
+\r
+ xmlfile.close();\r
+ }\r
+\r
+ bool all_ok() const\r
+ {\r
+ return failed_tests.empty();\r
+ };\r
+\r
+\r
+};\r
+\r
+}\r
+\r
+#endif\r
+\r
--- /dev/null
+#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;
+};
+
+/**
+ * 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
+ * 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;
+};
+
+}
+
+#endif
--- /dev/null
+#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 'tut_fork' in ctor of test object", self != NULL);
+
+ return self->fork_();
+ }
+
+ pid_t waitpid(pid_t pid, int *status, int flags = 0)
+ {
+ test_object<T> *self = dynamic_cast< tut::test_object<T>* >(this);
+ ensure("trying to call 'tut_waitpid' in ctor of test object", self != NULL);
+
+ return self->waitpid_(pid, status, flags);
+ }
+
+ 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().clear_callbacks();
+
+ // 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;
+ }
+
+ struct fdclose
+ {
+ fdclose(int fd): fd_(fd) { }
+ ~fdclose()
+ {
+ close(fd_);
+ }
+ private:
+ int fd_;
+ };
+
+ 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];
+ fdclose guard(pipe);
+
+ 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
+
--- /dev/null
+#ifndef TUT_REPORTER
+#define TUT_REPORTER
+
+#include <tut/tut_console_reporter.hpp>
+
+namespace tut
+{
+ typedef console_reporter reporter;
+}
+
+#endif
--- /dev/null
+#ifndef TUT_RESTARTABLE_H_GUARD
+#define TUT_RESTARTABLE_H_GUARD
+
+#include <tut/tut.hpp>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+#include <cassert>
+
+/**
+ * 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;
+ case test_result::rethrown:
+ os << 5;
+ break;
+ case test_result::ex_ctor:
+ os << 6;
+ break;
+ case test_result::dummy:
+ assert(!"Should never be called");
+ default:
+ throw std::logic_error("operator << : bad result_type");
+ }
+ os << ' ' << escape(tr.message) << std::endl;
+}
+
+/**
+ * deserialization for test_result
+ */
+bool deserialize(std::istream& is, tut::test_result& tr)
+{
+ std::getline(is,tr.group);
+ if (is.eof())
+ {
+ return false;
+ }
+ 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;
+ case 5:
+ tr.result = test_result::rethrown;
+ break;
+ case 6:
+ tr.result = test_result::ex_ctor;
+ 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");
+ }
+ return true;
+}
+};
+
+/**
+ * Restartable test runner wrapper.
+ */
+class restartable_wrapper
+{
+ test_runner& runner_;
+ callbacks callbacks_;
+
+ 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()),
+ 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)
+ {
+ callbacks_.clear();
+ callbacks_.insert(cb);
+ }
+
+ void insert_callback(callback* cb)
+ {
+ callbacks_.insert(cb);
+ }
+
+ void erase_callback(callback* cb)
+ {
+ callbacks_.erase(cb);
+ }
+
+ void set_callbacks(const callbacks& cb)
+ {
+ callbacks_ = cb;
+ }
+
+ const callbacks& get_callbacks() const
+ {
+ return runner_.get_callbacks();
+ }
+
+ /**
+ * 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);
+
+ tut::test_result tr;
+ if( !runner_.run_test(*gni,test, tr) || tr.result == test_result::dummy )
+ {
+ break;
+ }
+ register_test_(tr);
+
+ ++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_callbacks(callbacks_);
+ runner_.cb_run_started_();
+
+ std::string current_group;
+ std::ifstream ijournal(jrn_.c_str());
+ while (ijournal.good())
+ {
+ tut::test_result tr;
+ if( !util::deserialize(ijournal,tr) )
+ {
+ break;
+ }
+ runner_.cb_test_completed_(tr);
+ }
+
+ runner_.cb_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
+
--- /dev/null
+#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
+ */
+ enum result_type
+ {
+ ok,
+ fail,
+ ex,
+ warn,
+ term,
+ ex_ctor,
+ rethrown,
+ dummy
+ };
+
+ 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
--- /dev/null
+#ifndef TUT_RUNNER_H_GUARD
+#define TUT_RUNNER_H_GUARD
+
+#include <string>
+#include <vector>
+#include <set>
+#include "tut_exception.hpp"
+
+namespace tut
+{
+
+/**
+ * Interface.
+ * Test group operations.
+ */
+struct group_base
+{
+ virtual ~group_base()
+ {
+ }
+
+ // execute tests iteratively
+ virtual void rewind() = 0;
+ virtual bool run_next(test_result &) = 0;
+
+ // execute one test
+ virtual bool run_test(int n, test_result &tr) = 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
+{
+ /**
+ * Default constructor.
+ */
+ 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()
+ {
+ }
+private:
+ callback(const callback &);
+ void operator=(const callback&);
+};
+
+/**
+ * Typedef for runner::list_groups()
+ */
+typedef std::vector<std::string> groupnames;
+typedef std::set<callback*> callbacks;
+
+/**
+ * Test runner.
+ */
+class test_runner
+{
+
+public:
+
+ /**
+ * Constructor
+ */
+ test_runner()
+ {
+ }
+
+ /**
+ * 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) );
+ }
+
+ void set_callback(callback *cb)
+ {
+ clear_callbacks();
+ insert_callback(cb);
+ }
+
+ /**
+ * Stores callback object.
+ */
+ void insert_callback(callback* cb)
+ {
+ if(cb != NULL)
+ {
+ callbacks_.insert(cb);
+ }
+ }
+
+ void erase_callback(callback* cb)
+ {
+ callbacks_.erase(cb);
+ }
+
+ void clear_callbacks()
+ {
+ callbacks_.clear();
+ }
+
+ /**
+ * Returns callback list.
+ */
+ const callbacks &get_callbacks() const
+ {
+ return callbacks_;
+ }
+
+ void set_callbacks(const callbacks &cb)
+ {
+ callbacks_ = cb;
+ }
+
+ /**
+ * 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
+ {
+ cb_run_started_();
+
+ const_iterator i = groups_.begin();
+ const_iterator e = groups_.end();
+ while (i != e)
+ {
+ cb_group_started_(i->first);
+ run_all_tests_in_group_(i);
+ cb_group_completed_(i->first);
+
+ ++i;
+ }
+
+ cb_run_completed_();
+ }
+
+ /**
+ * Runs all tests in specified group.
+ */
+ void run_tests(const std::string& group_name) const
+ {
+ cb_run_started_();
+
+ const_iterator i = groups_.find(group_name);
+ if (i == groups_.end())
+ {
+ cb_run_completed_();
+ throw no_such_group(group_name);
+ }
+
+ cb_group_started_(group_name);
+ run_all_tests_in_group_(i);
+ cb_group_completed_(group_name);
+ cb_run_completed_();
+ }
+
+ /**
+ * Runs one test in specified group.
+ */
+ bool run_test(const std::string& group_name, int n, test_result &tr) const
+ {
+ cb_run_started_();
+
+ const_iterator i = groups_.find(group_name);
+ if (i == groups_.end())
+ {
+ cb_run_completed_();
+ throw no_such_group(group_name);
+ }
+
+ cb_group_started_(group_name);
+
+ bool t = i->second->run_test(n, tr);
+
+ if(t && tr.result != test_result::dummy)
+ {
+ cb_test_completed_(tr);
+ }
+
+ cb_group_completed_(group_name);
+ cb_run_completed_();
+
+ return t;
+ }
+
+protected:
+
+ typedef std::map<std::string, group_base*> groups;
+ typedef groups::iterator iterator;
+ typedef groups::const_iterator const_iterator;
+ groups groups_;
+
+ callbacks callbacks_;
+
+private:
+ friend class restartable_wrapper;
+
+ void cb_run_started_() const
+ {
+ for(callbacks::const_iterator i = callbacks_.begin(); i != callbacks_.end(); ++i)
+ {
+ (*i)->run_started();
+ }
+ }
+
+ void cb_run_completed_() const
+ {
+ for(callbacks::const_iterator i = callbacks_.begin(); i != callbacks_.end(); ++i)
+ {
+ (*i)->run_completed();
+ }
+ }
+
+ void cb_group_started_(const std::string &group_name) const
+ {
+ for(callbacks::const_iterator i = callbacks_.begin(); i != callbacks_.end(); ++i)
+ {
+ (*i)->group_started(group_name);
+ }
+ }
+
+ void cb_group_completed_(const std::string &group_name) const
+ {
+ for(callbacks::const_iterator i = callbacks_.begin(); i != callbacks_.end(); ++i)
+ {
+ (*i)->group_completed(group_name);
+ }
+ }
+
+ void cb_test_completed_(const test_result &tr) const
+ {
+ for(callbacks::const_iterator i = callbacks_.begin(); i != callbacks_.end(); ++i)
+ {
+ (*i)->test_completed(tr);
+ }
+ }
+
+ void run_all_tests_in_group_(const_iterator i) const
+ {
+ i->second->rewind();
+
+ test_result tr;
+ while(i->second->run_next(tr))
+ {
+ if(tr.result != test_result::dummy)
+ {
+ cb_test_completed_(tr);
+ }
+
+ if (tr.result == test_result::ex_ctor)
+ {
+ // test object ctor failed, skip whole group
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * 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
--- /dev/null
+/*
+ * tut_xml_reporter.hpp
+ *
+ * ECOS Library. IPT CS R&D CET ECOS Copyright 2008 Nokia
+ * Siemens Networks. All right
+ *
+ *
+ */
+
+#ifndef TUT_XML_REPORTER
+#define TUT_XML_REPORTER
+
+#include <tut/tut.hpp>
+#include <string>
+#include <fstream>
+#include <vector>
+#include <stdexcept>
+
+namespace tut
+{
+
+/**
+ * \brief JUnit XML TUT reporter
+ * @author Lukasz Maszczynski, NSN
+ * @date 11/07/2008
+ */
+class xml_reporter : public tut::callback
+{
+protected:
+ typedef std::vector<tut::test_result> TestResults;
+ typedef std::map<std::string, TestResults> TestGroups;
+
+ TestGroups all_tests; /// holds all test results
+ std::string filename; /// filename base
+
+ /**
+ * \brief Initializes object
+ * Resets counters and clears all stored test results.
+ */
+ virtual void init()
+ {
+ ok_count = 0;
+ exceptions_count = 0;
+ failures_count = 0;
+ terminations_count = 0;
+ warnings_count = 0;
+ all_tests.clear();
+ }
+
+ /**
+ * \brief Encodes text to XML
+ * XML-reserved characters (e.g. "<") are encoded according to specification
+ * @param text text to be encoded
+ * @return encoded string
+ */
+ virtual std::string encode(const std::string & text)
+ {
+ std::string out;
+
+ for (unsigned int i=0; i<text.length(); ++i) {
+ char c = text[i];
+ switch (c) {
+ case '<':
+ out += "<";
+ break;
+ case '>':
+ out += ">";
+ break;
+ case '&':
+ out += "&";
+ break;
+ case '\'':
+ out += "'";
+ break;
+ case '"':
+ out += """;
+ break;
+ default:
+ out += c;
+ }
+ }
+
+ return out;
+ }
+
+ /**
+ * \brief Builds "testcase" XML entity with given parameters
+ * Builds \<testcase\> entity according to given parameters. \<testcase\>-s are part of \<testsuite\>.
+ * @param tr test result to be used as source data
+ * @param failure_type type of failure to be reported ("Assertion" or "Error", empty if test passed)
+ * @param failure_msg failure message to be reported (empty, if test passed)
+ * @return string with \<testcase\> entity
+ */
+ virtual std::string xml_build_testcase(const tut::test_result & tr, const std::string & failure_type,
+ const std::string & failure_msg, int pid = 0)
+ {
+ using std::endl;
+ using std::string;
+
+ std::ostringstream out;
+
+ if (tr.result == test_result::ok)
+ {
+ out << " <testcase classname=\"" << encode(tr.group) << "\" name=\"" << encode(tr.name) << "\" />";
+ }
+ else
+ {
+ string err_msg = encode(failure_msg + tr.message);
+
+ string tag; // determines tag name: "failure" or "error"
+ if ( tr.result == test_result::fail || tr.result == test_result::warn ||
+ tr.result == test_result::ex || tr.result == test_result::ex_ctor )
+ {
+ tag = "failure";
+ }
+ else
+ {
+ tag = "error";
+ }
+
+ out << " <testcase classname=\"" << encode(tr.group) << "\" name=\"" << encode(tr.name) << "\">" << endl;
+ out << " <" << tag << " message=\"" << err_msg << "\"" << " type=\"" << failure_type << "\"";
+#if defined(TUT_USE_POSIX)
+ if(pid != getpid())
+ {
+ out << " child=\"" << pid << "\"";
+ }
+#endif
+ out << ">" << err_msg << "</" << tag << ">" << endl;
+ out << " </testcase>";
+ }
+
+ return out.str();
+ }
+
+ /**
+ * \brief Builds "testsuite" XML entity
+ * Builds \<testsuite\> XML entity according to given parameters.
+ * @param errors number of errors to be reported
+ * @param failures number of failures to be reported
+ * @param total total number of tests to be reported
+ * @param name test suite name
+ * @param testcases encoded XML string containing testcases
+ * @return string with \<testsuite\> entity
+ */
+ virtual std::string xml_build_testsuite(int errors, int failures, int total,
+ const std::string & name, const std::string & testcases)
+ {
+ std::ostringstream out;
+
+ out << "<testsuite errors=\"" << errors << "\" failures=\"" << failures << "\" tests=\"" << total << "\" name=\"" << encode(name) << "\">" << std::endl;
+ out << testcases;
+ out << "</testsuite>";
+
+ return out.str();
+ }
+
+public:
+ int ok_count; /// number of passed tests
+ int exceptions_count; /// number of tests that threw exceptions
+ int failures_count; /// number of tests that failed
+ int terminations_count; /// number of tests that would terminate
+ int warnings_count; /// number of tests where destructors threw an exception
+
+ /**
+ * \brief Default constructor
+ * @param filename base filename
+ * @see setFilenameBase
+ */
+ xml_reporter(const std::string & _filename = "")
+ {
+ init();
+ setFilenameBase(_filename);
+ }
+
+ /**
+ * \brief Sets filename base for output
+ * @param _filename filename base
+ * Example usage:
+ * @code
+ * xml_reporter reporter;
+ * reporter.setFilenameBase("my_xml");
+ * @endcode
+ * The above code will instruct reporter to create my_xml_1.xml file for the first test group,
+ * my_xml_2.xml file for the second, and so on.
+ */
+ virtual void setFilenameBase(const std::string & _filename)
+ {
+ if (_filename == "")
+ {
+ filename = "testResult";
+ }
+ else
+ {
+ if (_filename.length() > 200)
+ {
+ throw(std::runtime_error("Filename too long!"));
+ }
+
+ filename = _filename;
+ }
+ }
+
+ /**
+ * \brief Callback function
+ * This function is called before the first test is executed. It initializes counters.
+ */
+ virtual void run_started()
+ {
+ init();
+ }
+
+ /**
+ * \brief Callback function
+ * This function is called when test completes. Counters are updated here, and test results stored.
+ */
+ virtual void test_completed(const tut::test_result& tr)
+ {
+ // update global statistics
+ switch (tr.result) {
+ case test_result::ok:
+ ok_count++;
+ break;
+ case test_result::fail:
+ case test_result::rethrown:
+ failures_count++;
+ break;
+ case test_result::ex:
+ case test_result::ex_ctor:
+ exceptions_count++;
+ break;
+ case test_result::warn:
+ warnings_count++;
+ break;
+ case test_result::term:
+ terminations_count++;
+ break;
+ } // switch
+
+ // add test result to results table
+ (all_tests[tr.group]).push_back(tr);
+ }
+
+ /**
+ * \brief Callback function
+ * This function is called when all tests are completed. It generates XML output
+ * to file(s). File name base can be set with \ref setFilenameBase.
+ */
+ virtual void run_completed()
+ {
+ using std::endl;
+ using std::string;
+
+ static int number = 1; // results file sequence number (testResult_<number>.xml)
+
+ // iterate over all test groups
+ TestGroups::const_iterator tgi;
+ for (tgi = all_tests.begin(); tgi != all_tests.end(); ++tgi) {
+ /* per-group statistics */
+ int passed = 0; // passed in single group
+ int exceptions = 0; // exceptions in single group
+ int failures = 0; // failures in single group
+ int terminations = 0; // terminations in single group
+ int warnings = 0; // warnings in single group
+ int errors = 0; // errors in single group
+
+ /* generate output filename */
+ char fn[256];
+ sprintf(fn, "%s_%d.xml", filename.c_str(), number++);
+
+ std::ofstream xmlfile;
+ xmlfile.open(fn, std::ios::in | std::ios::trunc);
+ if (!xmlfile.is_open()) {
+ throw (std::runtime_error("Cannot open file for output"));
+ }
+
+ /* *********************** header ***************************** */
+ xmlfile << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
+
+ // output is written to string stream buffer, because JUnit format <testsuite> tag
+ // contains statistics, which aren't known yet
+ std::ostringstream out;
+
+ // iterate over all test cases in the current test group
+ TestResults::const_iterator tri;
+ for (tri = (*tgi).second.begin(); tri != (*tgi).second.end(); ++tri) {
+ string failure_type; // string describing the failure type
+ string failure_msg; // a string with failure message
+
+ switch ((*tri).result) {
+ case test_result::ok:
+ passed++;
+ break;
+ case test_result::fail:
+ failure_type = "Assertion";
+ failure_msg = "";
+ failures++;
+ break;
+ case test_result::ex:
+ failure_type = "Assertion";
+ failure_msg = "Thrown exception: " + (*tri).exception_typeid + '\n';
+ exceptions++;
+ break;
+ case test_result::warn:
+ failure_type = "Assertion";
+ failure_msg = "Destructor failed.\n";
+ warnings++;
+ break;
+ case test_result::term:
+ failure_type = "Error";
+ failure_msg = "Test application terminated abnormally.\n";
+ terminations++;
+ break;
+ case test_result::ex_ctor:
+ failure_type = "Assertion";
+ failure_msg = "Constructor has thrown an exception: " + (*tri).exception_typeid + '\n';
+ exceptions++;
+ break;
+ case test_result::rethrown:
+ failure_type = "Assertion";
+ failure_msg = "Child failed";
+ failures++;
+ break;
+ default:
+ failure_type = "Error";
+ failure_msg = "Unknown test status, this should have never happened. "
+ "You may just have found a BUG in TUT XML reporter, please report it immediately.\n";
+ errors++;
+ break;
+ } // switch
+
+#if defined(TUT_USE_POSIX)
+ out << xml_build_testcase(*tri, failure_type, failure_msg, (*tri).pid) << endl;
+#else
+ out << xml_build_testcase(*tri, failure_type, failure_msg) << endl;
+#endif
+
+ } // iterate over all test cases
+
+ // calculate per-group statistics
+ int stat_errors = terminations + errors;
+ int stat_failures = failures + warnings + exceptions;
+ int stat_all = stat_errors + stat_failures + passed;
+
+ xmlfile << xml_build_testsuite(stat_errors, stat_failures, stat_all, (*tgi).first/* name */, out.str()/* testcases */) << endl;
+ xmlfile.close();
+ } // iterate over all test groups
+ }
+
+ /**
+ * \brief Returns true, if all tests passed
+ */
+ virtual bool all_ok() const
+ {
+ return ( (terminations_count + failures_count + warnings_count + exceptions_count) == 0);
+ };
+};
+
+}
+
+#endif
--- /dev/null
+
+#include <tut/tut_console_reporter.hpp>
+#include <tut/tut_cppunit_reporter.hpp>
+#include <tut/tut_xml_reporter.hpp>
+#include <tut/tut_reporter.hpp>
--- /dev/null
+
+#include <tut/tut_restartable.hpp>