]> git.stg.codes - stg.git/commitdiff
Додано заголовочні файлі фреймворка для unit-тестування TUT
authorMaxim Mamontov <faust.madf@gmail.com>
Sat, 18 Dec 2010 16:41:02 +0000 (18:41 +0200)
committerMaxim Mamontov <faust.madf@gmail.com>
Sat, 18 Dec 2010 16:41:02 +0000 (18:41 +0200)
14 files changed:
tests/tut.h [new file with mode: 0644]
tests/tut/tut.hpp [new file with mode: 0644]
tests/tut/tut_assert.hpp [new file with mode: 0644]
tests/tut/tut_console_reporter.hpp [new file with mode: 0644]
tests/tut/tut_cppunit_reporter.hpp [new file with mode: 0644]
tests/tut/tut_exception.hpp [new file with mode: 0644]
tests/tut/tut_posix.hpp [new file with mode: 0644]
tests/tut/tut_reporter.hpp [new file with mode: 0644]
tests/tut/tut_restartable.hpp [new file with mode: 0644]
tests/tut/tut_result.hpp [new file with mode: 0644]
tests/tut/tut_runner.hpp [new file with mode: 0644]
tests/tut/tut_xml_reporter.hpp [new file with mode: 0644]
tests/tut_reporter.h [new file with mode: 0644]
tests/tut_restartable.h [new file with mode: 0644]

diff --git a/tests/tut.h b/tests/tut.h
new file mode 100644 (file)
index 0000000..22859cd
--- /dev/null
@@ -0,0 +1,2 @@
+
+#include <tut/tut.hpp>
diff --git a/tests/tut/tut.hpp b/tests/tut/tut.hpp
new file mode 100644 (file)
index 0000000..93233a6
--- /dev/null
@@ -0,0 +1,535 @@
+#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
+
diff --git a/tests/tut/tut_assert.hpp b/tests/tut/tut_assert.hpp
new file mode 100644 (file)
index 0000000..7ee44bc
--- /dev/null
@@ -0,0 +1,201 @@
+#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
+
diff --git a/tests/tut/tut_console_reporter.hpp b/tests/tut/tut_console_reporter.hpp
new file mode 100644 (file)
index 0000000..3c3739b
--- /dev/null
@@ -0,0 +1,241 @@
+#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
diff --git a/tests/tut/tut_cppunit_reporter.hpp b/tests/tut/tut_cppunit_reporter.hpp
new file mode 100644 (file)
index 0000000..3b9f38c
--- /dev/null
@@ -0,0 +1,201 @@
+\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 += "&lt;";\r
+                        break;\r
+                    case '>':\r
+                        out += "&gt;";\r
+                        break;\r
+                    case '&':\r
+                        out += "&amp;";\r
+                        break;\r
+                    case '\'':\r
+                        out += "&apos;";\r
+                        break;\r
+                    case '"':\r
+                        out += "&quot;";\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
diff --git a/tests/tut/tut_exception.hpp b/tests/tut/tut_exception.hpp
new file mode 100644 (file)
index 0000000..c5c88cb
--- /dev/null
@@ -0,0 +1,159 @@
+#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
diff --git a/tests/tut/tut_posix.hpp b/tests/tut/tut_posix.hpp
new file mode 100644 (file)
index 0000000..89a7130
--- /dev/null
@@ -0,0 +1,472 @@
+#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
+
diff --git a/tests/tut/tut_reporter.hpp b/tests/tut/tut_reporter.hpp
new file mode 100644 (file)
index 0000000..56731d8
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef TUT_REPORTER
+#define TUT_REPORTER
+
+#include <tut/tut_console_reporter.hpp>
+
+namespace tut
+{
+    typedef console_reporter reporter;
+}
+
+#endif
diff --git a/tests/tut/tut_restartable.hpp b/tests/tut/tut_restartable.hpp
new file mode 100644 (file)
index 0000000..eb6eb00
--- /dev/null
@@ -0,0 +1,409 @@
+#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
+
diff --git a/tests/tut/tut_result.hpp b/tests/tut/tut_result.hpp
new file mode 100644 (file)
index 0000000..5731802
--- /dev/null
@@ -0,0 +1,129 @@
+#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
diff --git a/tests/tut/tut_runner.hpp b/tests/tut/tut_runner.hpp
new file mode 100644 (file)
index 0000000..33ffe42
--- /dev/null
@@ -0,0 +1,359 @@
+#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
diff --git a/tests/tut/tut_xml_reporter.hpp b/tests/tut/tut_xml_reporter.hpp
new file mode 100644 (file)
index 0000000..8a9d5de
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+ * 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 += "&lt;";
+                    break;
+                case '>':
+                    out += "&gt;";
+                    break;
+                case '&':
+                    out += "&amp;";
+                    break;
+                case '\'':
+                    out += "&apos;";
+                    break;
+                case '"':
+                    out += "&quot;";
+                    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
diff --git a/tests/tut_reporter.h b/tests/tut_reporter.h
new file mode 100644 (file)
index 0000000..e568635
--- /dev/null
@@ -0,0 +1,5 @@
+
+#include <tut/tut_console_reporter.hpp>
+#include <tut/tut_cppunit_reporter.hpp>
+#include <tut/tut_xml_reporter.hpp>
+#include <tut/tut_reporter.hpp>
diff --git a/tests/tut_restartable.h b/tests/tut_restartable.h
new file mode 100644 (file)
index 0000000..c789779
--- /dev/null
@@ -0,0 +1,2 @@
+
+#include <tut/tut_restartable.hpp>