From ff51a32be1b1d113d4e7d71139e33e5b8ea243d2 Mon Sep 17 00:00:00 2001
From: onqtam <vik.kirilov@gmail.com>
Date: Sun, 24 Mar 2019 17:28:52 +0200
Subject: [PATCH] updated doctest to version 2.3.1 released today

---
 test/thirdparty/doctest/doctest.h | 1953 ++++++++++++++++++++---------
 1 file changed, 1342 insertions(+), 611 deletions(-)

diff --git a/test/thirdparty/doctest/doctest.h b/test/thirdparty/doctest/doctest.h
index eda93fc7..30d7c293 100644
--- a/test/thirdparty/doctest/doctest.h
+++ b/test/thirdparty/doctest/doctest.h
@@ -1,10 +1,10 @@
-// ======================================================================
+// ====================================================================== lgtm [cpp/missing-header-guard]
 // == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! ==
 // ======================================================================
 //
 // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
 //
-// Copyright (c) 2016-2018 Viktor Kirilov
+// Copyright (c) 2016-2019 Viktor Kirilov
 //
 // Distributed under the MIT Software License
 // See accompanying file LICENSE.txt or copy at
@@ -17,9 +17,9 @@
 // =================================================================================================
 // =================================================================================================
 //
-// The library is heavily influenced by Catch - https://github.com/philsquared/Catch
+// The library is heavily influenced by Catch - https://github.com/catchorg/Catch2
 // which uses the Boost Software License - Version 1.0
-// see here - https://github.com/philsquared/Catch/blob/master/LICENSE.txt
+// see here - https://github.com/catchorg/Catch2/blob/master/LICENSE.txt
 //
 // The concept of subcases (sections in Catch) and expression decomposition are from there.
 // Some parts of the code are taken directly:
@@ -29,6 +29,7 @@
 // - breaking into a debugger
 // - signal / SEH handling
 // - timer
+// - XmlWriter class - thanks to Phil Nash for allowing the direct reuse (AKA copy/paste)
 //
 // The expression decomposing templates are taken from lest - https://github.com/martinmoene/lest
 // which uses the Boost Software License - Version 1.0
@@ -46,9 +47,9 @@
 // =================================================================================================
 
 #define DOCTEST_VERSION_MAJOR 2
-#define DOCTEST_VERSION_MINOR 2
+#define DOCTEST_VERSION_MINOR 3
 #define DOCTEST_VERSION_PATCH 1
-#define DOCTEST_VERSION_STR "2.2.1"
+#define DOCTEST_VERSION_STR "2.3.1"
 
 #define DOCTEST_VERSION                                                                            \
     (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
@@ -210,7 +211,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
     DOCTEST_MSVC_SUPPRESS_WARNING(5027)                                                            \
     DOCTEST_MSVC_SUPPRESS_WARNING(5026)                                                            \
     DOCTEST_MSVC_SUPPRESS_WARNING(4623)                                                            \
-    DOCTEST_MSVC_SUPPRESS_WARNING(5039)
+    DOCTEST_MSVC_SUPPRESS_WARNING(5039)                                                            \
+    DOCTEST_MSVC_SUPPRESS_WARNING(5045)
 
 #define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
 
@@ -222,13 +224,15 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
 // MSVC C++11 feature support table: https://msdn.microsoft.com/en-us/library/hh567368.aspx
 // GCC C++11 feature support table: https://gcc.gnu.org/projects/cxx-status.html
 // MSVC version table:
-// MSVC++ 15.0 _MSC_VER == 1910 (Visual Studio 2017)
-// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)
-// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)
-// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)
-// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)
-// MSVC++ 9.0  _MSC_VER == 1500 (Visual Studio 2008)
-// MSVC++ 8.0  _MSC_VER == 1400 (Visual Studio 2005)
+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+// MSVC++ 14.2 (16) _MSC_VER == 1920 (Visual Studio 2019) << NOT YET RELEASED - April 2 2019
+// MSVC++ 14.1 (15) _MSC_VER == 1910 (Visual Studio 2017)
+// MSVC++ 14.0      _MSC_VER == 1900 (Visual Studio 2015)
+// MSVC++ 12.0      _MSC_VER == 1800 (Visual Studio 2013)
+// MSVC++ 11.0      _MSC_VER == 1700 (Visual Studio 2012)
+// MSVC++ 10.0      _MSC_VER == 1600 (Visual Studio 2010)
+// MSVC++ 9.0       _MSC_VER == 1500 (Visual Studio 2008)
+// MSVC++ 8.0       _MSC_VER == 1400 (Visual Studio 2005)
 
 #if DOCTEST_MSVC && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
 #define DOCTEST_CONFIG_WINDOWS_SEH
@@ -237,7 +241,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
 #undef DOCTEST_CONFIG_WINDOWS_SEH
 #endif // DOCTEST_CONFIG_NO_WINDOWS_SEH
 
-#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS)
+#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) &&             \
+        !defined(__EMSCRIPTEN__)
 #define DOCTEST_CONFIG_POSIX_SIGNALS
 #endif // _WIN32
 #if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)
@@ -245,12 +250,9 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
 #endif // DOCTEST_CONFIG_NO_POSIX_SIGNALS
 
 #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
-#if(DOCTEST_GCC || (DOCTEST_CLANG && !DOCTEST_MSVC)) && !defined(__EXCEPTIONS)
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
 #define DOCTEST_CONFIG_NO_EXCEPTIONS
-#endif // clang and gcc
-#if DOCTEST_MSVC && (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS == 0)
-#define DOCTEST_CONFIG_NO_EXCEPTIONS
-#endif // MSVC
+#endif // no exceptions
 #endif // DOCTEST_CONFIG_NO_EXCEPTIONS
 
 #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS
@@ -290,6 +292,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
 #define DOCTEST_INTERFACE
 #endif // DOCTEST_CONFIG_IMPLEMENTATION_IN_DLL
 
+#define DOCTEST_EMPTY
+
 #if DOCTEST_MSVC
 #define DOCTEST_NOINLINE __declspec(noinline)
 #define DOCTEST_UNUSED
@@ -317,6 +321,8 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
 #define DOCTEST_ANONYMOUS(x) DOCTEST_CAT(x, __LINE__)
 #endif // __COUNTER__
 
+#define DOCTEST_TOSTR(x) #x
+
 #ifndef DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
 #define DOCTEST_REF_WRAP(x) x&
 #else // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
@@ -324,7 +330,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom constr
 #endif // DOCTEST_CONFIG_ASSERTION_PARAMETERS_BY_VALUE
 
 // not using __APPLE__ because... this is how Catch does it
-#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
+#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
 #define DOCTEST_PLATFORM_MAC
 #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
 #define DOCTEST_PLATFORM_IPHONE
@@ -359,40 +365,49 @@ extern "C" __declspec(dllimport) void __stdcall DebugBreak();
 #define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0)
 #endif // linux
 
+// this is kept here for backwards compatibility since the config option was changed
+#ifdef DOCTEST_CONFIG_USE_IOSFWD
+#define DOCTEST_CONFIG_USE_STD_HEADERS
+#endif // DOCTEST_CONFIG_USE_IOSFWD
+
+#ifdef DOCTEST_CONFIG_USE_STD_HEADERS
+#include <iosfwd>
+#include <cstddef>
+#else // DOCTEST_CONFIG_USE_STD_HEADERS
+
 #if DOCTEST_CLANG
 // to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier)
 #include <ciso646>
 #endif // clang
 
+#ifdef _LIBCPP_VERSION
+#define DOCTEST_STD_NAMESPACE_BEGIN _LIBCPP_BEGIN_NAMESPACE_STD
+#define DOCTEST_STD_NAMESPACE_END _LIBCPP_END_NAMESPACE_STD
+#else // _LIBCPP_VERSION
+#define DOCTEST_STD_NAMESPACE_BEGIN namespace std {
+#define DOCTEST_STD_NAMESPACE_END }
+#endif // _LIBCPP_VERSION
+
 // Forward declaring 'X' in namespace std is not permitted by the C++ Standard.
 DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4643)
 
-#if defined(_LIBCPP_VERSION) || defined(DOCTEST_CONFIG_USE_IOSFWD)
-// not forward declaring ostream for libc++ because I had some problems (inline namespaces vs c++98)
-// so the <iosfwd> header is used - also it is very light and doesn't drag a ton of stuff
-#include <iosfwd>
-#else  // _LIBCPP_VERSION
-namespace std {
+DOCTEST_STD_NAMESPACE_BEGIN
+typedef decltype(nullptr) nullptr_t;
 template <class charT>
 struct char_traits;
 template <>
 struct char_traits<char>;
 template <class charT, class traits>
 class basic_ostream;
-typedef basic_ostream<char, char_traits<char> > ostream;
-} // namespace std
-#endif // _LIBCPP_VERSION || DOCTEST_CONFIG_USE_IOSFWD
-
-#ifdef _LIBCPP_VERSION
-#include <cstddef>
-#else  // _LIBCPP_VERSION
-namespace std {
-typedef decltype(nullptr) nullptr_t;
-}
-#endif // _LIBCPP_VERSION
+typedef basic_ostream<char, char_traits<char>> ostream;
+template <class... Types>
+class tuple;
+DOCTEST_STD_NAMESPACE_END
 
 DOCTEST_MSVC_SUPPRESS_WARNING_POP
 
+#endif // DOCTEST_CONFIG_USE_STD_HEADERS
+
 #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
 #include <type_traits>
 #endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
@@ -449,6 +464,7 @@ public:
     String();
     ~String();
 
+    // cppcheck-suppress noExplicitConstructor
     String(const char* in);
     String(const char* in, unsigned in_size);
 
@@ -600,7 +616,7 @@ namespace assertType {
 
 DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
 DOCTEST_INTERFACE const char* failureString(assertType::Enum at);
-DOCTEST_INTERFACE const char* removePathFromFilename(const char* file);
+DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file);
 
 struct DOCTEST_INTERFACE TestCaseData
 {
@@ -680,7 +696,12 @@ struct DOCTEST_INTERFACE IContextScope
 
 struct ContextOptions //!OCLINT too many fields
 {
+    std::ostream* cout;        // stdout stream - std::cout by default
+    std::ostream* cerr;        // stderr stream - std::cerr by default
+    String        binary_name; // the test binary name
+
     // == parameters from the command line
+    String   out;       // output filename
     String   order_by;  // how tests should be ordered
     unsigned rand_seed; // the seed for rand ordering
 
@@ -764,7 +785,7 @@ namespace detail {
         {
             static std::ostream& s;
             static const DOCTEST_REF_WRAP(T) t;
-            static const bool value = sizeof(testStreamable(s << t)) == sizeof(yes);
+            static const bool value = sizeof(decltype(testStreamable(s << t))) == sizeof(yes);
         };
     } // namespace has_insertion_operator_impl
 
@@ -1004,6 +1025,9 @@ namespace detail {
         return Result(res);                                                                        \
     }
 
+    // more checks could be added - like in Catch:
+    // https://github.com/catchorg/Catch2/pull/1480/files
+    // https://github.com/catchorg/Catch2/pull/1481/files
 #define DOCTEST_FORBIT_EXPRESSION(rt, op)                                                          \
     template <typename R>                                                                          \
     rt& operator op(const R&) {                                                                    \
@@ -1188,8 +1212,8 @@ namespace detail {
 
         // The right operator for capturing expressions is "<=" instead of "<<" (based on the operator precedence table)
         // but then there will be warnings from GCC about "-Wparentheses" and since "_Pragma()" is problematic this will stay for now...
-        // https://github.com/philsquared/Catch/issues/870
-        // https://github.com/philsquared/Catch/issues/565
+        // https://github.com/catchorg/Catch2/issues/870
+        // https://github.com/catchorg/Catch2/issues/565
         template <typename L>
         Expression_lhs<const DOCTEST_REF_WRAP(L)> operator<<(const DOCTEST_REF_WRAP(L) operand) {
             return Expression_lhs<const DOCTEST_REF_WRAP(L)>(operand, m_at);
@@ -1403,10 +1427,10 @@ namespace detail {
         explicit ExceptionTranslator(String (*translateFunction)(T))
                 : m_translateFunction(translateFunction) {}
 
-        bool translate(String& res) const {
+        bool translate(String& res) const override {
 #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
             try {
-                throw;
+                throw; // lgtm [cpp/rethrow-no-exception]
                 // cppcheck-suppress catchExceptionByValue
             } catch(T ex) {                    // NOLINT
                 res = m_translateFunction(ex); //!OCLINT parameter reassignment
@@ -1423,21 +1447,17 @@ namespace detail {
 
     DOCTEST_INTERFACE void registerExceptionTranslatorImpl(const IExceptionTranslator* et);
 
-    // FIX FOR VISUAL STUDIO VERSIONS PRIOR TO 2015 - they failed to compile the call to operator<< with
-    // std::ostream passed as a reference noting that there is a use of an undefined type (which there isn't)
-    DOCTEST_INTERFACE void writeStringToStream(std::ostream* s, const String& str);
-
     template <bool C>
     struct StringStreamBase
     {
         template <typename T>
         static void convert(std::ostream* s, const T& in) {
-            writeStringToStream(s, toString(in));
+            *s << toString(in);
         }
 
         // always treat char* as a string in this context - no matter
         // if DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING is defined
-        static void convert(std::ostream* s, const char* in) { writeStringToStream(s, String(in)); }
+        static void convert(std::ostream* s, const char* in) { *s << String(in); }
     };
 
     template <>
@@ -1699,17 +1719,21 @@ namespace TestCaseFailureReason {
 
 struct DOCTEST_INTERFACE CurrentTestCaseStats
 {
-    int    numAssertsForCurrentTestCase;
-    int    numAssertsFailedForCurrentTestCase;
-    double seconds_so_far;
+    int    numAssertsCurrentTest;
+    int    numAssertsFailedCurrentTest;
+    double seconds;
     int    failure_flags; // use TestCaseFailureReason::Enum
-    String error_string;
-    bool   should_reenter; // means we are not done with the test case because of subcases
 
     DOCTEST_DECLARE_DEFAULTS(CurrentTestCaseStats);
     DOCTEST_DELETE_COPIES(CurrentTestCaseStats);
 };
 
+struct DOCTEST_INTERFACE TestCaseException
+{
+    String error_string;
+    bool   is_crash;
+};
+
 struct DOCTEST_INTERFACE TestRunStats
 {
     unsigned numTestCases;
@@ -1723,23 +1747,39 @@ struct DOCTEST_INTERFACE TestRunStats
     DOCTEST_DELETE_COPIES(TestRunStats);
 };
 
+struct QueryData
+{
+    const TestRunStats* run_stats = nullptr;
+    String*             data      = nullptr;
+    unsigned            num_data  = 0;
+};
+
 struct DOCTEST_INTERFACE IReporter
 {
-    // called when the whole test run starts (safe to cache a pointer to the input)
-    virtual void test_run_start(const ContextOptions&) = 0;
+    // The constructor has to accept "const ContextOptions&" as a single argument
+    // which has most of the options for the run + a pointer to the stdout stream
+    // Reporter(const ContextOptions& in)
+
+    // called when a query should be reported (listing test cases, printing the version, etc.)
+    virtual void report_query(const QueryData&) = 0;
+
+    // called when the whole test run starts
+    virtual void test_run_start() = 0;
     // called when the whole test run ends (caching a pointer to the input doesn't make sense here)
     virtual void test_run_end(const TestRunStats&) = 0;
 
     // called when a test case is started (safe to cache a pointer to the input)
     virtual void test_case_start(const TestCaseData&) = 0;
-    // called when a test case has ended - could be re-entered if more subcases have to be
-    // traversed - check CurrentTestCaseStats::should_reenter (caching a pointer to the input doesn't make sense here)
+    // called when a test case has ended
     virtual void test_case_end(const CurrentTestCaseStats&) = 0;
 
+    // called when an exception is thrown from the test case (or it crashes)
+    virtual void test_case_exception(const TestCaseException&) = 0;
+
     // called whenever a subcase is entered (don't cache pointers to the input)
     virtual void subcase_start(const SubcaseSignature&) = 0;
     // called whenever a subcase is exited (don't cache pointers to the input)
-    virtual void subcase_end(const SubcaseSignature&) = 0;
+    virtual void subcase_end() = 0;
 
     // called for each assert (don't cache pointers to the input)
     virtual void log_assert(const AssertData&) = 0;
@@ -1762,8 +1802,22 @@ struct DOCTEST_INTERFACE IReporter
     static const String* get_stringified_contexts();
 };
 
-int registerReporter(const char* name, int priority, IReporter& r);
+namespace detail {
+    typedef IReporter* (*reporterCreatorFunc)(const ContextOptions&);
 
+    DOCTEST_INTERFACE void registerReporterImpl(const char* name, int prio, reporterCreatorFunc c);
+
+    template <typename Reporter>
+    IReporter* reporterCreator(const ContextOptions& o) {
+        return new Reporter(o);
+    }
+} // namespace detail
+
+template <typename Reporter>
+int registerReporter(const char* name, int priority) {
+    detail::registerReporterImpl(name, priority, detail::reporterCreator<Reporter>);
+    return 0;
+}
 } // namespace doctest
 
 // if registering is not disabled
@@ -1785,11 +1839,13 @@ int registerReporter(const char* name, int priority, IReporter& r);
 #endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS
 
 // registers the test by initializing a dummy var with a function
-#define DOCTEST_REGISTER_FUNCTION(f, decorators)                                                   \
-    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) = doctest::detail::regTest(  \
-            doctest::detail::TestCase(f, __FILE__, __LINE__,                                       \
-                                      doctest_detail_test_suite_ns::getCurrentTestSuite()) *       \
-            decorators);                                                                           \
+#define DOCTEST_REGISTER_FUNCTION(global_prefix, f, decorators)                                    \
+    global_prefix DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_VAR_)) =              \
+            doctest::detail::regTest(                                                              \
+                    doctest::detail::TestCase(                                                     \
+                            f, __FILE__, __LINE__,                                                 \
+                            doctest_detail_test_suite_ns::getCurrentTestSuite()) *                 \
+                    decorators);                                                                   \
     DOCTEST_GLOBAL_NO_WARNINGS_END()
 
 #define DOCTEST_IMPLEMENT_FIXTURE(der, base, func, decorators)                                     \
@@ -1802,19 +1858,35 @@ int registerReporter(const char* name, int priority, IReporter& r);
             der v;                                                                                 \
             v.f();                                                                                 \
         }                                                                                          \
-        DOCTEST_REGISTER_FUNCTION(func, decorators)                                                \
+        DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, func, decorators)                                 \
     }                                                                                              \
     inline DOCTEST_NOINLINE void der::f()
 
 #define DOCTEST_CREATE_AND_REGISTER_FUNCTION(f, decorators)                                        \
     static void f();                                                                               \
-    DOCTEST_REGISTER_FUNCTION(f, decorators)                                                       \
+    DOCTEST_REGISTER_FUNCTION(DOCTEST_EMPTY, f, decorators)                                        \
+    static void f()
+
+#define DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(f, proxy, decorators)                        \
+    static doctest::detail::funcType proxy() { return f; }                                         \
+    DOCTEST_REGISTER_FUNCTION(inline const, proxy(), decorators)                                   \
     static void f()
 
 // for registering tests
 #define DOCTEST_TEST_CASE(decorators)                                                              \
     DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), decorators)
 
+// for registering tests in classes - requires C++17 for inline variables!
+#if __cplusplus >= 201703L || (DOCTEST_MSVC >= DOCTEST_COMPILER(19, 12, 0) && _MSVC_LANG >= 201703L)
+#define DOCTEST_TEST_CASE_CLASS(decorators)                                                        \
+    DOCTEST_CREATE_AND_REGISTER_FUNCTION_IN_CLASS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_),          \
+                                                  DOCTEST_ANONYMOUS(_DOCTEST_ANON_PROXY_),         \
+                                                  decorators)
+#else // DOCTEST_TEST_CASE_CLASS
+#define DOCTEST_TEST_CASE_CLASS(...)                                                               \
+    TEST_CASES_CAN_BE_REGISTERED_IN_CLASSES_ONLY_IN_CPP17_MODE_OR_WITH_VS_2017_OR_NEWER
+#endif // DOCTEST_TEST_CASE_CLASS
+
 // for registering tests with a fixture
 #define DOCTEST_TEST_CASE_FIXTURE(c, decorators)                                                   \
     DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), c,                          \
@@ -1841,49 +1913,58 @@ int registerReporter(const char* name, int priority, IReporter& r);
                                       doctest::detail::type_to_string<type>(), idx) *              \
             decorators)
 
-#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, id, anon)                                   \
+#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, iter, func)                                 \
     template <typename T>                                                                          \
-    inline void anon();                                                                            \
+    inline void func();                                                                            \
+    template <typename Tuple>                                                                      \
+    struct iter;                                                                                   \
     template <typename Type, typename... Rest>                                                     \
-    struct DOCTEST_CAT(id, ITERATOR)                                                               \
+    struct iter<std::tuple<Type, Rest...>>                                                         \
     {                                                                                              \
-        DOCTEST_CAT(id, ITERATOR)(int line, int index) {                                           \
-            DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(anon<Type>, Type, dec, line * 1000 + index);     \
-            DOCTEST_CAT(id, ITERATOR)<Rest...>(line, index + 1);                                   \
+        iter(int line, int index) {                                                                \
+            DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(func<Type>, Type, dec, line * 1000 + index);     \
+            iter<std::tuple<Rest...>>(line, index + 1);                                            \
         }                                                                                          \
     };                                                                                             \
-    template <typename Type>                                                                       \
-    struct DOCTEST_CAT(id, ITERATOR)<Type>                                                         \
+    template <>                                                                                    \
+    struct iter<std::tuple<>>                                                                      \
     {                                                                                              \
-        DOCTEST_CAT(id, ITERATOR)(int line, int index) {                                           \
-            DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(anon<Type>, Type, dec, line * 1000 + index);     \
-        }                                                                                          \
-    }
-
-#define DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL_PROXY(dec, T, id, anon)                             \
-    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, id, anon);                                      \
+        iter(int, int) {}                                                                          \
+    };                                                                                             \
     template <typename T>                                                                          \
-    inline void anon()
+    inline void func()
 
 #define DOCTEST_TEST_CASE_TEMPLATE_DEFINE(dec, T, id)                                              \
-    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL_PROXY(dec, T, id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_))
+    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(id, ITERATOR),                      \
+                                           DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_))
 
-#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, anon, ...)                                 \
-    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = []() {                                  \
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE_IMPL(id, anon, ...)                                      \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = [] {                                    \
+        DOCTEST_CAT(id, ITERATOR)<std::tuple<__VA_ARGS__>> DOCTEST_UNUSED DOCTEST_CAT(             \
+                anon, inner_dummy)(__LINE__, 0);                                                   \
+        return 0;                                                                                  \
+    }();                                                                                           \
+    DOCTEST_GLOBAL_NO_WARNINGS_END()
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...)                                                 \
+    DOCTEST_TEST_CASE_TEMPLATE_INVOKE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__) \
+    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY_IMPL(id, anon, ...)                                       \
+    DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_CAT(anon, DUMMY)) = [] {                                    \
         DOCTEST_CAT(id, ITERATOR)<__VA_ARGS__> DOCTEST_UNUSED DOCTEST_CAT(anon, inner_dummy)(      \
                 __LINE__, 0);                                                                      \
         return 0;                                                                                  \
     }();                                                                                           \
     DOCTEST_GLOBAL_NO_WARNINGS_END()
 
-#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...)                                            \
-    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_),         \
-                                                __VA_ARGS__)                                       \
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...)                                                  \
+    DOCTEST_TEST_CASE_TEMPLATE_APPLY_IMPL(id, DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_), __VA_ARGS__)  \
     typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
 
 #define DOCTEST_TEST_CASE_TEMPLATE_IMPL(dec, T, anon, ...)                                         \
-    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL_PROXY(dec, T, anon, anon);                              \
-    DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE_IMPL(anon, anon, __VA_ARGS__)                           \
+    DOCTEST_TEST_CASE_TEMPLATE_DEFINE_IMPL(dec, T, DOCTEST_CAT(anon, ITERATOR), anon);             \
+    DOCTEST_TEST_CASE_TEMPLATE_INVOKE_IMPL(anon, anon, __VA_ARGS__)                                \
     template <typename T>                                                                          \
     inline void anon()
 
@@ -1947,7 +2028,7 @@ int registerReporter(const char* name, int priority, IReporter& r);
 // for registering
 #define DOCTEST_REGISTER_REPORTER(name, priority, reporter)                                        \
     DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) =                       \
-            doctest::registerReporter(name, priority, reporter);                                   \
+            doctest::registerReporter<reporter>(name, priority);                                   \
     DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
 
 // for logging
@@ -2026,17 +2107,7 @@ constexpr T to_lvalue = x;
 #define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while((void)0, 0)
 // clang-format on
 
-#define DOCTEST_ASSERT_THROWS(expr, assert_type)                                                   \
-    do {                                                                                           \
-        if(!doctest::getContextOptions()->no_throw) {                                              \
-            doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
-                                                       __LINE__, #expr);                           \
-            try {                                                                                  \
-                expr;                                                                              \
-            } catch(...) { _DOCTEST_RB.m_threw = true; }                                           \
-            DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                             \
-        }                                                                                          \
-    } while((void)0, 0)
+#define DOCTEST_ASSERT_THROWS(expr, assert_type) DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, "")
 
 #define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, ...)                                           \
     do {                                                                                           \
@@ -2047,7 +2118,7 @@ constexpr T to_lvalue = x;
                 expr;                                                                              \
             } catch(const doctest::detail::remove_const<                                           \
                     doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) {                \
-                _DOCTEST_RB.m_threw    = true;                                                     \
+                _DOCTEST_RB.translateException();                                                  \
                 _DOCTEST_RB.m_threw_as = true;                                                     \
             } catch(...) { _DOCTEST_RB.translateException(); }                                     \
             DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB);                                             \
@@ -2263,6 +2334,10 @@ constexpr T to_lvalue = x;
 #define DOCTEST_TEST_CASE(name)                                                                    \
     DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
 
+// for registering tests in classes
+#define DOCTEST_TEST_CASE_CLASS(name)                                                              \
+    DOCTEST_CREATE_AND_REGISTER_FUNCTION(DOCTEST_ANONYMOUS(_DOCTEST_ANON_FUNC_), name)
+
 // for registering tests with a fixture
 #define DOCTEST_TEST_CASE_FIXTURE(x, name)                                                         \
     DOCTEST_IMPLEMENT_FIXTURE(DOCTEST_ANONYMOUS(_DOCTEST_ANON_CLASS_), x,                          \
@@ -2281,7 +2356,10 @@ constexpr T to_lvalue = x;
     template <typename type>                                                                       \
     inline void DOCTEST_ANONYMOUS(_DOCTEST_ANON_TMP_)()
 
-#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...)                                            \
+#define DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, ...)                                                 \
+    typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
+#define DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, ...)                                                  \
     typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
 
 // for subcases
@@ -2406,11 +2484,14 @@ constexpr T to_lvalue = x;
 #define DOCTEST_FAST_WARN_UNARY_FALSE    DOCTEST_WARN_UNARY_FALSE
 #define DOCTEST_FAST_CHECK_UNARY_FALSE   DOCTEST_CHECK_UNARY_FALSE
 #define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
+
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE
 // clang-format on
 
 // BDD style macros
 // clang-format off
 #define DOCTEST_SCENARIO(name) DOCTEST_TEST_CASE("  Scenario: " name)
+#define DOCTEST_SCENARIO_CLASS(name) DOCTEST_TEST_CASE_CLASS("  Scenario: " name)
 #define DOCTEST_SCENARIO_TEMPLATE(name, T, ...)  DOCTEST_TEST_CASE_TEMPLATE("  Scenario: " name, T, __VA_ARGS__)
 #define DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE("  Scenario: " name, T, id)
 
@@ -2425,11 +2506,13 @@ constexpr T to_lvalue = x;
 #if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
 
 #define TEST_CASE DOCTEST_TEST_CASE
+#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS
 #define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE
 #define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING
 #define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE
 #define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE
-#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE
+#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE
+#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY
 #define SUBCASE DOCTEST_SUBCASE
 #define TEST_SUITE DOCTEST_TEST_SUITE
 #define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN
@@ -2485,6 +2568,7 @@ constexpr T to_lvalue = x;
 #define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE
 
 #define SCENARIO DOCTEST_SCENARIO
+#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS
 #define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE
 #define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE
 #define GIVEN DOCTEST_GIVEN
@@ -2537,6 +2621,7 @@ constexpr T to_lvalue = x;
 #define FAST_WARN_LE DOCTEST_FAST_WARN_LE
 #define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE
 #define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE
+
 #define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY
 #define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY
 #define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY
@@ -2544,6 +2629,8 @@ constexpr T to_lvalue = x;
 #define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE
 #define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE
 
+#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE
+
 #endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
 
 #if !defined(DOCTEST_CONFIG_DISABLE)
@@ -2560,7 +2647,9 @@ namespace doctest { namespace detail {
     DOCTEST_TYPE_TO_STRING_IMPL(char)
     DOCTEST_TYPE_TO_STRING_IMPL(signed char)
     DOCTEST_TYPE_TO_STRING_IMPL(unsigned char)
+#if !DOCTEST_MSVC || defined(_NATIVE_WCHAR_T_DEFINED)
     DOCTEST_TYPE_TO_STRING_IMPL(wchar_t)
+#endif // not MSVC or wchar_t support enabled
     DOCTEST_TYPE_TO_STRING_IMPL(short int)
     DOCTEST_TYPE_TO_STRING_IMPL(unsigned short int)
     DOCTEST_TYPE_TO_STRING_IMPL(int)
@@ -2611,6 +2700,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-braces")
 DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")
 DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
 DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
 
 DOCTEST_GCC_SUPPRESS_WARNING_PUSH
 DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")
@@ -2631,6 +2721,7 @@ DOCTEST_GCC_SUPPRESS_WARNING("-Wunsafe-loop-optimizations")
 DOCTEST_GCC_SUPPRESS_WARNING("-Wold-style-cast")
 DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-local-typedefs")
 DOCTEST_GCC_SUPPRESS_WARNING("-Wuseless-cast")
+DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-function")
 
 DOCTEST_MSVC_SUPPRESS_WARNING_PUSH
 DOCTEST_MSVC_SUPPRESS_WARNING(4616) // invalid compiler warning
@@ -2675,6 +2766,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 #include <cstring>
 #include <limits>
 #include <utility>
+#include <fstream>
 #include <sstream>
 #include <iostream>
 #include <algorithm>
@@ -2686,7 +2778,9 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 #include <map>
 #include <exception>
 #include <stdexcept>
+#ifdef DOCTEST_CONFIG_POSIX_SIGNALS
 #include <csignal>
+#endif // DOCTEST_CONFIG_POSIX_SIGNALS
 #include <cfloat>
 #include <cctype>
 #include <cstdint>
@@ -2697,6 +2791,34 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 #include <sys/sysctl.h>
 #endif // DOCTEST_PLATFORM_MAC
 
+#ifdef DOCTEST_PLATFORM_WINDOWS
+
+// defines for a leaner windows.h
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif // WIN32_LEAN_AND_MEAN
+#ifndef VC_EXTRA_LEAN
+#define VC_EXTRA_LEAN
+#endif // VC_EXTRA_LEAN
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif // NOMINMAX
+
+// not sure what AfxWin.h is for - here I do what Catch does
+#ifdef __AFXDLL
+#include <AfxWin.h>
+#else
+#include <Windows.h>
+#endif
+#include <io.h>
+
+#else // DOCTEST_PLATFORM_WINDOWS
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#endif // DOCTEST_PLATFORM_WINDOWS
+
 DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
 
 // counts the number of elements in a C array
@@ -2760,14 +2882,12 @@ namespace {
         };
 
         static Arch which() {
-            union _
-            {
-                int  asInt;
-                char asChar[sizeof(int)];
-            } u;
-
-            u.asInt = 1;                                            // NOLINT
-            return (u.asChar[sizeof(int) - 1] == 1) ? Big : Little; // NOLINT
+            int x = 1;
+            // casting any data pointer to char* is allowed
+            auto ptr = reinterpret_cast<char*>(&x);
+            if(*ptr)
+                return Little;
+            return Big;
         }
     };
 } // namespace
@@ -2806,13 +2926,52 @@ namespace detail {
     }
 
 #ifndef DOCTEST_CONFIG_DISABLE
+
+    typedef uint64_t UInt64;
+
+#ifdef DOCTEST_CONFIG_GETCURRENTTICKS
+    UInt64 getCurrentTicks() { return DOCTEST_CONFIG_GETCURRENTTICKS(); }
+#elif defined(DOCTEST_PLATFORM_WINDOWS)
+    UInt64 getCurrentTicks() {
+        static UInt64 hz = 0, hzo = 0;
+        if(!hz) {
+            QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&hz));
+            QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&hzo));
+        }
+        UInt64 t;
+        QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&t));
+        return ((t - hzo) * 1000000) / hz;
+    }
+#else  // DOCTEST_PLATFORM_WINDOWS
+    UInt64 getCurrentTicks() {
+        timeval t;
+        gettimeofday(&t, nullptr);
+        return static_cast<UInt64>(t.tv_sec) * 1000000 + static_cast<UInt64>(t.tv_usec);
+    }
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+    struct Timer
+    {
+        void         start() { m_ticks = getCurrentTicks(); }
+        unsigned int getElapsedMicroseconds() const {
+            return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
+        }
+        //unsigned int getElapsedMilliseconds() const {
+        //    return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);
+        //}
+        double getElapsedSeconds() const { return getElapsedMicroseconds() / 1000000.0; }
+
+    private:
+        UInt64 m_ticks = 0;
+    };
+
     // this holds both parameters from the command line and runtime data for tests
     struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
     {
-        std::atomic<int> numAssertsForCurrentTestCase_atomic;
-        std::atomic<int> numAssertsFailedForCurrentTestCase_atomic;
+        std::atomic<int> numAssertsCurrentTest_atomic;
+        std::atomic<int> numAssertsFailedCurrentTest_atomic;
 
-        std::vector<std::vector<String> > filters = decltype(filters)(9); // 9 different filters
+        std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
 
         std::vector<IReporter*> reporters_currently_used;
 
@@ -2820,12 +2979,15 @@ namespace detail {
 
         assert_handler ah = nullptr;
 
+        Timer timer;
+
         std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
 
         // stuff for subcases
         std::set<SubcaseSignature> subcasesPassed;
         std::set<int>              subcasesEnteredLevels;
         int                        subcasesCurrentLevel;
+        bool                       should_reenter;
 
         void resetRunData() {
             numTestCases                = 0;
@@ -2834,11 +2996,58 @@ namespace detail {
             numTestCasesFailed          = 0;
             numAsserts                  = 0;
             numAssertsFailed            = 0;
+            numAssertsCurrentTest       = 0;
+            numAssertsFailedCurrentTest = 0;
+        }
+
+        void finalizeTestCaseData() {
+            seconds = timer.getElapsedSeconds();
+
+            // update the non-atomic counters
+            numAsserts += numAssertsCurrentTest_atomic;
+            numAssertsFailed += numAssertsFailedCurrentTest_atomic;
+            numAssertsCurrentTest       = numAssertsCurrentTest_atomic;
+            numAssertsFailedCurrentTest = numAssertsFailedCurrentTest_atomic;
+
+            if(numAssertsFailedCurrentTest)
+                failure_flags |= TestCaseFailureReason::AssertFailure;
+
+            if(Approx(currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
+               Approx(seconds).epsilon(DBL_EPSILON) > currentTest->m_timeout)
+                failure_flags |= TestCaseFailureReason::Timeout;
+
+            if(currentTest->m_should_fail) {
+                if(failure_flags) {
+                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
+                } else {
+                    failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt;
+                }
+            } else if(failure_flags && currentTest->m_may_fail) {
+                failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid;
+            } else if(currentTest->m_expected_failures > 0) {
+                if(numAssertsFailedCurrentTest == currentTest->m_expected_failures) {
+                    failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes;
+                } else {
+                    failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes;
+                }
+            }
+
+            bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & failure_flags) ||
+                              (TestCaseFailureReason::CouldHaveFailedAndDid & failure_flags) ||
+                              (TestCaseFailureReason::FailedExactlyNumTimes & failure_flags);
+
+            // if any subcase has failed - the whole test case has failed
+            if(failure_flags && !ok_to_fail)
+                numTestCasesFailed++;
         }
     };
 
-    ContextState*             g_cs = nullptr;
-    DOCTEST_THREAD_LOCAL bool g_no_colors; // used to avoid locks for the debug output
+    ContextState* g_cs = nullptr;
+
+    // used to avoid locks for the debug output
+    // TODO: figure out if this is indeed necessary/correct - seems like either there still
+    // could be a race or that there wouldn't be a race even if using the context directly
+    DOCTEST_THREAD_LOCAL bool g_no_colors;
 
 #endif // DOCTEST_CONFIG_DISABLE
 } // namespace detail
@@ -3082,18 +3291,18 @@ const char* assertString(assertType::Enum at) {
 
 const char* failureString(assertType::Enum at) {
     if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
-        return "WARNING: ";
+        return "WARNING";
     if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
-        return "ERROR: ";
+        return "ERROR";
     if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
-        return "FATAL ERROR: ";
+        return "FATAL ERROR";
     return "";
 }
 
 DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
 DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference")
 // depending on the current options this will remove the path of filenames
-const char* removePathFromFilename(const char* file) {
+const char* skipPathFromFilename(const char* file) {
     if(getContextOptions()->no_path_in_filenames) {
         auto back    = std::strrchr(file, '\\');
         auto forward = std::strrchr(file, '/');
@@ -3253,52 +3462,6 @@ int registerReporter(const char*, int, IReporter*) { return 0; }
 #endif // DOCTEST_CONFIG_COLORS_WINDOWS && DOCTEST_CONFIG_COLORS_ANSI
 #endif // DOCTEST_CONFIG_COLORS_NONE
 
-#if DOCTEST_MSVC || defined(__MINGW32__)
-#if DOCTEST_MSVC
-#define DOCTEST_WINDOWS_SAL_IN_OPT _In_opt_
-#else // MSVC
-#define DOCTEST_WINDOWS_SAL_IN_OPT
-#endif // MSVC
-extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(
-        DOCTEST_WINDOWS_SAL_IN_OPT const char*);
-extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent();
-#endif // MSVC || __MINGW32__
-
-#ifdef DOCTEST_CONFIG_COLORS_ANSI
-#include <unistd.h>
-#endif // DOCTEST_CONFIG_COLORS_ANSI
-
-#ifdef DOCTEST_PLATFORM_WINDOWS
-
-// defines for a leaner windows.h
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif // WIN32_LEAN_AND_MEAN
-#ifndef VC_EXTRA_LEAN
-#define VC_EXTRA_LEAN
-#endif // VC_EXTRA_LEAN
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif // NOMINMAX
-
-DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
-
-// not sure what AfxWin.h is for - here I do what Catch does
-#ifdef __AFXDLL
-#include <AfxWin.h>
-#else
-#include <Windows.h>
-#endif
-#include <io.h>
-
-DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
-
-#else // DOCTEST_PLATFORM_WINDOWS
-
-#include <sys/time.h>
-
-#endif // DOCTEST_PLATFORM_WINDOWS
-
 namespace doctest_detail_test_suite_ns {
 // holds the current test suite
 doctest::detail::TestSuite& getCurrentTestSuite() {
@@ -3309,9 +3472,10 @@ doctest::detail::TestSuite& getCurrentTestSuite() {
 
 namespace doctest {
 namespace {
-    using namespace detail;
-    typedef std::map<std::pair<int, String>, IReporter*> reporterMap;
-    reporterMap&                                         getReporters() {
+    // the int (priority) is part of the key for automatic sorting - sadly one can register a
+    // reporter with a duplicate name and a different priority but hopefully that won't happen often :|
+    typedef std::map<std::pair<int, String>, reporterCreatorFunc> reporterMap;
+    reporterMap&                                                  getReporters() {
         static reporterMap data;
         return data;
     }
@@ -3329,7 +3493,7 @@ namespace detail {
 
         if((at & assertType::is_check) //!OCLINT bitwise operator in conditional
            && getContextOptions()->abort_after > 0 &&
-           (g_cs->numAssertsFailed + g_cs->numAssertsFailedForCurrentTestCase_atomic) >=
+           (g_cs->numAssertsFailed + g_cs->numAssertsFailedCurrentTest_atomic) >=
                    getContextOptions()->abort_after)
             return true;
 
@@ -3402,48 +3566,6 @@ namespace {
                 return true;
         return false;
     }
-
-#ifdef DOCTEST_PLATFORM_WINDOWS
-
-    typedef unsigned long long UInt64;
-
-    UInt64 getCurrentTicks() {
-        static UInt64 hz = 0, hzo = 0;
-        if(!hz) {
-            QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&hz));
-            QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&hzo));
-        }
-        UInt64 t;
-        QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&t));
-        return ((t - hzo) * 1000000) / hz;
-    }
-#else  // DOCTEST_PLATFORM_WINDOWS
-
-    typedef uint64_t UInt64;
-
-    UInt64 getCurrentTicks() {
-        timeval t;
-        gettimeofday(&t, nullptr);
-        return static_cast<UInt64>(t.tv_sec) * 1000000 + static_cast<UInt64>(t.tv_usec);
-    }
-#endif // DOCTEST_PLATFORM_WINDOWS
-
-    struct Timer
-    {
-        void         start() { m_ticks = getCurrentTicks(); }
-        unsigned int getElapsedMicroseconds() const {
-            return static_cast<unsigned int>(getCurrentTicks() - m_ticks);
-        }
-        //unsigned int getElapsedMilliseconds() const {
-        //    return static_cast<unsigned int>(getElapsedMicroseconds() / 1000);
-        //}
-        double getElapsedSeconds() const { return getElapsedMicroseconds() / 1000000.0; }
-
-    private:
-        UInt64 m_ticks = 0;
-    };
-
-    Timer g_timer;
 } // namespace
 namespace detail {
 
@@ -3484,7 +3606,7 @@ namespace detail {
             if(s->should_reenter == false)
                 s->subcasesPassed.insert(m_signature);
 
-            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, m_signature);
+            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
         }
     }
 
@@ -3581,9 +3703,7 @@ namespace detail {
 namespace {
     using namespace detail;
     // for sorting tests by file/line
-    int fileOrderComparator(const void* a, const void* b) {
-        auto lhs = *static_cast<TestCase* const*>(a);
-        auto rhs = *static_cast<TestCase* const*>(b);
+    bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) {
 #if DOCTEST_MSVC
         // this is needed because MSVC gives different case for drive letters
         // for __FILE__ when evaluated in a header and a source file
@@ -3592,30 +3712,26 @@ namespace {
         const int res = std::strcmp(lhs->m_file, rhs->m_file);
 #endif // MSVC
         if(res != 0)
-            return res;
-        return static_cast<int>(lhs->m_line) - static_cast<int>(rhs->m_line);
+            return res < 0;
+        if(lhs->m_line != rhs->m_line)
+            return lhs->m_line < rhs->m_line;
+        return lhs->m_template_id < rhs->m_template_id;
     }
 
     // for sorting tests by suite/file/line
-    int suiteOrderComparator(const void* a, const void* b) {
-        auto lhs = *static_cast<TestCase* const*>(a);
-        auto rhs = *static_cast<TestCase* const*>(b);
-
+    bool suiteOrderComparator(const TestCase* lhs, const TestCase* rhs) {
         const int res = std::strcmp(lhs->m_test_suite, rhs->m_test_suite);
         if(res != 0)
-            return res;
-        return fileOrderComparator(a, b);
+            return res < 0;
+        return fileOrderComparator(lhs, rhs);
     }
 
     // for sorting tests by name/suite/file/line
-    int nameOrderComparator(const void* a, const void* b) {
-        auto lhs = *static_cast<TestCase* const*>(a);
-        auto rhs = *static_cast<TestCase* const*>(b);
-
-        const int res_name = std::strcmp(lhs->m_name, rhs->m_name);
-        if(res_name != 0)
-            return res_name;
-        return suiteOrderComparator(a, b);
+    bool nameOrderComparator(const TestCase* lhs, const TestCase* rhs) {
+        const int res = std::strcmp(lhs->m_name, rhs->m_name);
+        if(res != 0)
+            return res < 0;
+        return suiteOrderComparator(lhs, rhs);
     }
 
     // all the registered tests
@@ -3772,8 +3888,7 @@ namespace detail {
         // Call sysctl.
         size = sizeof(info);
         if(sysctl(mib, DOCTEST_COUNTOF(mib), &info, &size, 0, 0) != 0) {
-            fprintf(stderr, "\n** Call to sysctl failed - unable to determine if debugger is "
-                            "active **\n\n");
+            std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n";
             return false;
         }
         // We're being debugged if the P_TRACED flag is set.
@@ -3791,8 +3906,6 @@ namespace detail {
             getExceptionTranslators().push_back(et);
     }
 
-    void writeStringToStream(std::ostream* s, const String& str) { *s << str; }
-
 #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING
     void toStream(std::ostream* s, char* in) { *s << in; }
     void toStream(std::ostream* s, const char* in) { *s << in; }
@@ -3889,6 +4002,16 @@ namespace detail {
 } // namespace detail
 namespace {
     using namespace detail;
+
+    std::ostream& file_line_to_stream(std::ostream& s, const char* file, int line,
+                                      const char* tail = "") {
+        const auto opt = getContextOptions();
+        s << Color::LightGrey << skipPathFromFilename(file) << (opt->gnu_file_line ? ":" : "(")
+          << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option
+          << (opt->gnu_file_line ? ":" : "):") << tail;
+        return s;
+    }
+
 #if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
     struct FatalConditionHandler
     {
@@ -3902,7 +4025,7 @@ namespace {
 
     struct SignalDefs
     {
-        DWORD       id;
+        DWORD id;
         const char* name;
     };
     // There is no 1-1 mapping between signals and windows exceptions.
@@ -3932,7 +4055,7 @@ namespace {
             isSet = true;
             // 32k seems enough for doctest to handle stack overflow,
             // but the value was found experimentally, so there is no strong guarantee
-            guaranteeSize          = 32 * 1024;
+            guaranteeSize = 32 * 1024;
             exceptionHandlerHandle = nullptr;
             // Register as first handler in current chain
             exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException);
@@ -3946,20 +4069,20 @@ namespace {
                 RemoveVectoredExceptionHandler(exceptionHandlerHandle);
                 SetThreadStackGuarantee(&guaranteeSize);
                 exceptionHandlerHandle = nullptr;
-                isSet                  = false;
+                isSet = false;
             }
         }
 
         ~FatalConditionHandler() { reset(); }
 
     private:
-        static bool  isSet;
+        static bool isSet;
         static ULONG guaranteeSize;
         static PVOID exceptionHandlerHandle;
     };
 
-    bool  FatalConditionHandler::isSet                  = false;
-    ULONG FatalConditionHandler::guaranteeSize          = 0;
+    bool FatalConditionHandler::isSet = false;
+    ULONG FatalConditionHandler::guaranteeSize = 0;
     PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr;
 
 #else // DOCTEST_PLATFORM_WINDOWS
@@ -4048,27 +4171,27 @@ namespace {
 
     void addAssert(assertType::Enum at) {
         if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
-            g_cs->numAssertsForCurrentTestCase_atomic++;
+            g_cs->numAssertsCurrentTest_atomic++;
     }
 
     void addFailedAssert(assertType::Enum at) {
         if((at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
-            g_cs->numAssertsFailedForCurrentTestCase_atomic++;
+            g_cs->numAssertsFailedCurrentTest_atomic++;
     }
 
 #if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)
     void reportFatal(const std::string& message) {
-        g_cs->seconds_so_far += g_timer.getElapsedSeconds();
         g_cs->failure_flags |= TestCaseFailureReason::Crash;
-        g_cs->error_string   = message.c_str();
-        g_cs->should_reenter = false;
 
-        // TODO: end all currently opened subcases...?
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception, {message.c_str(), true});
+
+        while(g_cs->subcasesCurrentLevel--)
+            DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, DOCTEST_EMPTY);
+
+        g_cs->finalizeTestCaseData();
 
         DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
 
-        g_cs->numTestCasesFailed++;
-
         DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
     }
 #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
@@ -4189,21 +4312,655 @@ namespace detail {
     MessageBuilder::~MessageBuilder() = default;
 } // namespace detail
 namespace {
-    std::mutex g_mutex;
-
     using namespace detail;
+
+    template <typename Ex>
+    [[noreturn]] void throw_exception(Ex const& e) {
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+        throw e;
+#else  // DOCTEST_CONFIG_NO_EXCEPTIONS
+        std::cerr << "doctest will terminate because it needed to throw an exception.\n"
+                  << "The message was: " << e.what() << '\n';
+        std::terminate();
+#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+    }
+
+#define DOCTEST_INTERNAL_ERROR(msg)                                                                \
+    throw_exception(std::logic_error(                                                              \
+            __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg))
+
+    // clang-format off
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+    class XmlEncode {
+    public:
+        enum ForWhat { ForTextNodes, ForAttributes };
+
+        XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes );
+
+        void encodeTo( std::ostream& os ) const;
+
+        friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode );
+
+    private:
+        std::string m_str;
+        ForWhat m_forWhat;
+    };
+
+    class XmlWriter {
+    public:
+
+        class ScopedElement {
+        public:
+            ScopedElement( XmlWriter* writer );
+
+            ScopedElement( ScopedElement&& other ) noexcept;
+            ScopedElement& operator=( ScopedElement&& other ) noexcept;
+
+            ~ScopedElement();
+
+            ScopedElement& writeText( std::string const& text, bool indent = true );
+
+            template<typename T>
+            ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
+                m_writer->writeAttribute( name, attribute );
+                return *this;
+            }
+
+        private:
+            mutable XmlWriter* m_writer = nullptr;
+        };
+
+        XmlWriter( std::ostream& os = std::cout );
+        ~XmlWriter();
+
+        XmlWriter( XmlWriter const& ) = delete;
+        XmlWriter& operator=( XmlWriter const& ) = delete;
+
+        XmlWriter& startElement( std::string const& name );
+
+        ScopedElement scopedElement( std::string const& name );
+
+        XmlWriter& endElement();
+
+        XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
+
+        XmlWriter& writeAttribute( std::string const& name, const char* attribute );
+
+        XmlWriter& writeAttribute( std::string const& name, bool attribute );
+
+        template<typename T>
+        XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
+        std::stringstream rss;
+            rss << attribute;
+            return writeAttribute( name, rss.str() );
+        }
+
+        XmlWriter& writeText( std::string const& text, bool indent = true );
+
+        //XmlWriter& writeComment( std::string const& text );
+
+        //void writeStylesheetRef( std::string const& url );
+
+        //XmlWriter& writeBlankLine();
+
+        void ensureTagClosed();
+
+    private:
+
+        void writeDeclaration();
+
+        void newlineIfNecessary();
+
+        bool m_tagIsOpen = false;
+        bool m_needsNewline = false;
+        std::vector<std::string> m_tags;
+        std::string m_indent;
+        std::ostream& m_os;
+    };
+
+// =================================================================================================
+// The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp
+// This is done so cherry-picking bug fixes is trivial - even the style/formatting is untouched.
+// =================================================================================================
+
+using uchar = unsigned char;
+
+namespace {
+
+    size_t trailingBytes(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return 2;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return 3;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return 4;
+        }
+        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    uint32_t headerValue(unsigned char c) {
+        if ((c & 0xE0) == 0xC0) {
+            return c & 0x1F;
+        }
+        if ((c & 0xF0) == 0xE0) {
+            return c & 0x0F;
+        }
+        if ((c & 0xF8) == 0xF0) {
+            return c & 0x07;
+        }
+        DOCTEST_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered");
+    }
+
+    void hexEscapeChar(std::ostream& os, unsigned char c) {
+        std::ios_base::fmtflags f(os.flags());
+        os << "\\x"
+            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
+            << static_cast<int>(c);
+        os.flags(f);
+    }
+
+} // anonymous namespace
+
+    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
+    :   m_str( str ),
+        m_forWhat( forWhat )
+    {}
+
+    void XmlEncode::encodeTo( std::ostream& os ) const {
+        // Apostrophe escaping not necessary if we always use " to write attributes
+        // (see: http://www.w3.org/TR/xml/#syntax)
+
+        for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) {
+            uchar c = m_str[idx];
+            switch (c) {
+            case '<':   os << "&lt;"; break;
+            case '&':   os << "&amp;"; break;
+
+            case '>':
+                // See: http://www.w3.org/TR/xml/#syntax
+                if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']')
+                    os << "&gt;";
+                else
+                    os << c;
+                break;
+
+            case '\"':
+                if (m_forWhat == ForAttributes)
+                    os << "&quot;";
+                else
+                    os << c;
+                break;
+
+            default:
+                // Check for control characters and invalid utf-8
+
+                // Escape control characters in standard ascii
+                // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
+                if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // Plain ASCII: Write it to stream
+                if (c < 0x7F) {
+                    os << c;
+                    break;
+                }
+
+                // UTF-8 territory
+                // Check if the encoding is valid and if it is not, hex escape bytes.
+                // Important: We do not check the exact decoded values for validity, only the encoding format
+                // First check that this bytes is a valid lead byte:
+                // This means that it is not encoded as 1111 1XXX
+                // Or as 10XX XXXX
+                if (c <  0xC0 ||
+                    c >= 0xF8) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                auto encBytes = trailingBytes(c);
+                // Are there enough bytes left to avoid accessing out-of-bounds memory?
+                if (idx + encBytes - 1 >= m_str.size()) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+                // The header is valid, check data
+                // The next encBytes bytes must together be a valid utf-8
+                // This means: bitpattern 10XX XXXX and the extracted value is sane (ish)
+                bool valid = true;
+                uint32_t value = headerValue(c);
+                for (std::size_t n = 1; n < encBytes; ++n) {
+                    uchar nc = m_str[idx + n];
+                    valid &= ((nc & 0xC0) == 0x80);
+                    value = (value << 6) | (nc & 0x3F);
+                }
+
+                if (
+                    // Wrong bit pattern of following bytes
+                    (!valid) ||
+                    // Overlong encodings
+                    (value < 0x80) ||
+                    (                 value < 0x800   && encBytes > 2) || // removed "0x80 <= value &&" because redundant
+                    (0x800 < value && value < 0x10000 && encBytes > 3) ||
+                    // Encoded value out of range
+                    (value >= 0x110000)
+                    ) {
+                    hexEscapeChar(os, c);
+                    break;
+                }
+
+                // If we got here, this is in fact a valid(ish) utf-8 sequence
+                for (std::size_t n = 0; n < encBytes; ++n) {
+                    os << m_str[idx + n];
+                }
+                idx += encBytes - 1;
+                break;
+            }
+        }
+    }
+
+    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
+        xmlEncode.encodeTo( os );
+        return os;
+    }
+
+    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
+    :   m_writer( writer )
+    {}
+
+    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept
+    :   m_writer( other.m_writer ){
+        other.m_writer = nullptr;
+    }
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {
+        if ( m_writer ) {
+            m_writer->endElement();
+        }
+        m_writer = other.m_writer;
+        other.m_writer = nullptr;
+        return *this;
+    }
+
+
+    XmlWriter::ScopedElement::~ScopedElement() {
+        if( m_writer )
+            m_writer->endElement();
+    }
+
+    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
+        m_writer->writeText( text, indent );
+        return *this;
+    }
+
+    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
+    {
+        writeDeclaration();
+    }
+
+    XmlWriter::~XmlWriter() {
+        while( !m_tags.empty() )
+            endElement();
+    }
+
+    XmlWriter& XmlWriter::startElement( std::string const& name ) {
+        ensureTagClosed();
+        newlineIfNecessary();
+        m_os << m_indent << '<' << name;
+        m_tags.push_back( name );
+        m_indent += "  ";
+        m_tagIsOpen = true;
+        return *this;
+    }
+
+    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
+        ScopedElement scoped( this );
+        startElement( name );
+        return scoped;
+    }
+
+    XmlWriter& XmlWriter::endElement() {
+        newlineIfNecessary();
+        m_indent = m_indent.substr( 0, m_indent.size()-2 );
+        if( m_tagIsOpen ) {
+            m_os << "/>";
+            m_tagIsOpen = false;
+        }
+        else {
+            m_os << m_indent << "</" << m_tags.back() << ">";
+        }
+        m_os << std::endl;
+        m_tags.pop_back();
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
+        if( !name.empty() && !attribute.empty() )
+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, const char* attribute ) {
+        if( !name.empty() && attribute && attribute[0] != '\0' )
+            m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
+        m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
+        return *this;
+    }
+
+    XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
+        if( !text.empty() ){
+            bool tagWasOpen = m_tagIsOpen;
+            ensureTagClosed();
+            if( tagWasOpen && indent )
+                m_os << m_indent;
+            m_os << XmlEncode( text );
+            m_needsNewline = true;
+        }
+        return *this;
+    }
+
+    //XmlWriter& XmlWriter::writeComment( std::string const& text ) {
+    //    ensureTagClosed();
+    //    m_os << m_indent << "<!--" << text << "-->";
+    //    m_needsNewline = true;
+    //    return *this;
+    //}
+
+    //void XmlWriter::writeStylesheetRef( std::string const& url ) {
+    //    m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
+    //}
+
+    //XmlWriter& XmlWriter::writeBlankLine() {
+    //    ensureTagClosed();
+    //    m_os << '\n';
+    //    return *this;
+    //}
+
+    void XmlWriter::ensureTagClosed() {
+        if( m_tagIsOpen ) {
+            m_os << ">" << std::endl;
+            m_tagIsOpen = false;
+        }
+    }
+
+    void XmlWriter::writeDeclaration() {
+        m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+    }
+
+    void XmlWriter::newlineIfNecessary() {
+        if( m_needsNewline ) {
+            m_os << std::endl;
+            m_needsNewline = false;
+        }
+    }
+
+// =================================================================================================
+// End of copy-pasted code from Catch
+// =================================================================================================
+
+    // clang-format on
+
+    struct XmlReporter : public IReporter
+    {
+        XmlWriter  xml;
+        std::mutex mutex;
+
+        // caching pointers/references to objects of these types - safe to do
+        const ContextOptions& opt;
+        const TestCaseData*   tc = nullptr;
+
+        XmlReporter(const ContextOptions& co)
+                : xml(*co.cout)
+                , opt(co) {}
+
+        void log_contexts() {
+            int num_contexts = get_num_active_contexts();
+            if(num_contexts) {
+                auto              contexts = get_active_contexts();
+                std::stringstream ss;
+                for(int i = 0; i < num_contexts; ++i) {
+                    contexts[i]->stringify(&ss);
+                    xml.scopedElement("Info").writeText(ss.str());
+                    ss.str("");
+                }
+            }
+        }
+
+        unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; }
+
+        void test_case_start_impl(const TestCaseData& in) {
+            bool open_ts_tag = false;
+            if(tc != nullptr) { // we have already opened a test suite
+                if(strcmp(tc->m_test_suite, in.m_test_suite) != 0) {
+                    xml.endElement();
+                    open_ts_tag = true;
+                }
+            }
+            else {
+                open_ts_tag = true; // first test case ==> first test suite
+            }
+
+            if(open_ts_tag) {
+                xml.startElement("TestSuite");
+                xml.writeAttribute("name", in.m_test_suite);
+            }
+
+            tc = &in;
+            xml.startElement("TestCase")
+                    .writeAttribute("name", in.m_name)
+                    .writeAttribute("filename", skipPathFromFilename(in.m_file))
+                    .writeAttribute("line", line(in.m_line))
+                    .writeAttribute("description", in.m_description);
+
+            if(Approx(in.m_timeout) != 0)
+                xml.writeAttribute("timeout", in.m_timeout);
+            if(in.m_may_fail)
+                xml.writeAttribute("may_fail", true);
+            if(in.m_should_fail)
+                xml.writeAttribute("should_fail", true);
+        }
+
+        // =========================================================================================
+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+        // =========================================================================================
+
+        void report_query(const QueryData& in) override {
+            test_run_start();
+            if(opt.list_reporters) {
+                for(auto& curr : getReporters())
+                    xml.scopedElement("Reporter")
+                            .writeAttribute("priority", curr.first.first)
+                            .writeAttribute("name", curr.first.second);
+            } else if(opt.count || opt.list_test_cases) {
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    xml.scopedElement("TestCase").writeAttribute("name", in.data[i]);
+                xml.scopedElement("OverallResultsTestCases")
+                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+            } else if(opt.list_test_suites) {
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    xml.scopedElement("TestSuite").writeAttribute("name", in.data[i]);
+                xml.scopedElement("OverallResultsTestCases")
+                        .writeAttribute("unskipped", in.run_stats->numTestCasesPassingFilters);
+                xml.scopedElement("OverallResultsTestSuites")
+                        .writeAttribute("unskipped", in.run_stats->numTestSuitesPassingFilters);
+            }
+            xml.endElement();
+        }
+
+        void test_run_start() override {
+            // remove .exe extension - mainly to have the same output on UNIX and Windows
+            std::string binary_name = skipPathFromFilename(opt.binary_name.c_str());
+#ifdef DOCTEST_PLATFORM_WINDOWS
+            if(binary_name.rfind(".exe") != std::string::npos)
+                binary_name = binary_name.substr(0, binary_name.length() - 4);
+#endif // DOCTEST_PLATFORM_WINDOWS
+
+            xml.startElement("doctest").writeAttribute("binary", binary_name);
+            if(opt.no_version == false)
+                xml.writeAttribute("version", DOCTEST_VERSION_STR);
+
+            // only the consequential ones (TODO: filters)
+            xml.scopedElement("Options")
+                    .writeAttribute("order_by", opt.order_by.c_str())
+                    .writeAttribute("rand_seed", opt.rand_seed)
+                    .writeAttribute("first", opt.first)
+                    .writeAttribute("last", opt.last)
+                    .writeAttribute("abort_after", opt.abort_after)
+                    .writeAttribute("subcase_filter_levels", opt.subcase_filter_levels)
+                    .writeAttribute("case_sensitive", opt.case_sensitive)
+                    .writeAttribute("no_throw", opt.no_throw)
+                    .writeAttribute("no_skip", opt.no_skip);
+        }
+
+        void test_run_end(const TestRunStats& p) override {
+            if(tc) // the TestSuite tag - only if there has been at least 1 test case
+                xml.endElement();
+
+            xml.scopedElement("OverallResultsAsserts")
+                    .writeAttribute("successes", p.numAsserts - p.numAssertsFailed)
+                    .writeAttribute("failures", p.numAssertsFailed);
+
+            xml.startElement("OverallResultsTestCases")
+                    .writeAttribute("successes",
+                                    p.numTestCasesPassingFilters - p.numTestCasesFailed)
+                    .writeAttribute("failures", p.numTestCasesFailed);
+            if(opt.no_skipped_summary == false)
+                xml.writeAttribute("skipped", p.numTestCases - p.numTestCasesPassingFilters);
+            xml.endElement();
+
+            xml.endElement();
+        }
+
+        void test_case_start(const TestCaseData& in) override {
+            test_case_start_impl(in);
+            xml.ensureTagClosed();
+        }
+
+        void test_case_end(const CurrentTestCaseStats& st) override {
+            xml.startElement("OverallResultsAsserts")
+                    .writeAttribute("successes",
+                                    st.numAssertsCurrentTest - st.numAssertsFailedCurrentTest)
+                    .writeAttribute("failures", st.numAssertsFailedCurrentTest);
+            if(opt.duration)
+                xml.writeAttribute("duration", st.seconds);
+            if(tc->m_expected_failures)
+                xml.writeAttribute("expected_failures", tc->m_expected_failures);
+            xml.endElement();
+
+            xml.endElement();
+        }
+
+        void test_case_exception(const TestCaseException& e) override {
+            xml.scopedElement("Exception")
+                    .writeAttribute("crash", e.is_crash)
+                    .writeText(e.error_string.c_str());
+        }
+
+        void subcase_start(const SubcaseSignature& in) override {
+            xml.startElement("SubCase")
+                    .writeAttribute("name", in.m_name)
+                    .writeAttribute("filename", skipPathFromFilename(in.m_file))
+                    .writeAttribute("line", line(in.m_line));
+        }
+
+        void subcase_end() override { xml.endElement(); }
+
+        void log_assert(const AssertData& rb) override {
+            if(!rb.m_failed && !opt.success)
+                return;
+
+            std::lock_guard<std::mutex> lock(mutex);
+
+            xml.startElement("Expression")
+                    .writeAttribute("success", !rb.m_failed)
+                    .writeAttribute("type", assertString(rb.m_at))
+                    .writeAttribute("filename", skipPathFromFilename(rb.m_file))
+                    .writeAttribute("line", line(rb.m_line));
+
+            xml.scopedElement("Original").writeText(rb.m_expr);
+
+            if(rb.m_threw)
+                xml.scopedElement("Exception").writeText(rb.m_exception.c_str());
+
+            if(rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) {
+                xml.scopedElement("ExpectedException").writeText(rb.m_exception_type);
+            } else if((rb.m_at & assertType::is_normal) && !rb.m_threw) {
+                xml.scopedElement("Expanded").writeText(rb.m_decomp.c_str());
+            }
+
+            log_contexts();
+
+            xml.endElement();
+        }
+
+        void log_message(const MessageData& mb) override {
+            std::lock_guard<std::mutex> lock(mutex);
+
+            xml.startElement("Message")
+                    .writeAttribute("type", failureString(mb.m_severity))
+                    .writeAttribute("filename", skipPathFromFilename(mb.m_file))
+                    .writeAttribute("line", line(mb.m_line));
+
+            xml.scopedElement("Text").writeText(mb.m_string.c_str());
+
+            log_contexts();
+
+            xml.endElement();
+        }
+
+        void test_case_skipped(const TestCaseData& in) override {
+            if(opt.no_skipped_summary == false) {
+                test_case_start_impl(in);
+                xml.writeAttribute("skipped", "true");
+                xml.endElement();
+            }
+        }
+    };
+
+    DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter);
+
+    struct Whitespace
+    {
+        int nrSpaces;
+        explicit Whitespace(int nr)
+                : nrSpaces(nr) {}
+    };
+
+    std::ostream& operator<<(std::ostream& out, const Whitespace& ws) {
+        if(ws.nrSpaces != 0)
+            out << std::setw(ws.nrSpaces) << ' ';
+        return out;
+    }
+
     struct ConsoleReporter : public IReporter
     {
         std::ostream&                 s;
         bool                          hasLoggedCurrentTestStart;
         std::vector<SubcaseSignature> subcasesStack;
+        std::mutex                    mutex;
 
-        // caching pointers to objects of these types - safe to do
-        const ContextOptions* opt;
+        // caching pointers/references to objects of these types - safe to do
+        const ContextOptions& opt;
         const TestCaseData*   tc;
 
-        ConsoleReporter(std::ostream& in)
-                : s(in) {}
+        ConsoleReporter(const ContextOptions& co)
+                : s(*co.cout)
+                , opt(co) {}
+
+        ConsoleReporter(const ContextOptions& co, std::ostream& ostr)
+                : s(ostr)
+                , opt(co) {}
 
         // =========================================================================================
         // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE
@@ -4211,18 +4968,10 @@ namespace {
 
         void separator_to_stream() {
             s << Color::Yellow
-              << "============================================================================="
-                 "=="
+              << "==============================================================================="
                  "\n";
         }
 
-        void file_line_to_stream(const char* file, int line, const char* tail = "") {
-            s << Color::LightGrey << removePathFromFilename(file)
-              << (opt->gnu_file_line ? ":" : "(")
-              << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option
-              << (opt->gnu_file_line ? ":" : "):") << tail;
-        }
-
         const char* getSuccessOrFailString(bool success, assertType::Enum at,
                                            const char* success_str) {
             if(success)
@@ -4236,9 +4985,9 @@ namespace {
         }
 
         void successOrFailColoredStringToStream(bool success, assertType::Enum at,
-                                                const char* success_str = "SUCCESS: ") {
+                                                const char* success_str = "SUCCESS") {
             s << getSuccessOrFailColor(success, at)
-              << getSuccessOrFailString(success, at, success_str);
+              << getSuccessOrFailString(success, at, success_str) << ": ";
         }
 
         void log_contexts() {
@@ -4262,7 +5011,7 @@ namespace {
                 return;
 
             separator_to_stream();
-            file_line_to_stream(tc->m_file, tc->m_line, "\n");
+            file_line_to_stream(s, tc->m_file, tc->m_line, "\n");
             if(tc->m_description)
                 s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n";
             if(tc->m_test_suite && tc->m_test_suite[0] != '\0')
@@ -4280,199 +5029,8 @@ namespace {
             hasLoggedCurrentTestStart = true;
         }
 
-        // =========================================================================================
-        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
-        // =========================================================================================
-
-        void test_run_start(const ContextOptions& o) override { opt = &o; }
-
-        void test_run_end(const TestRunStats& p) override {
-            separator_to_stream();
-
-            const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
-            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6)
-              << p.numTestCasesPassingFilters << " | "
-              << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :
-                                                                          Color::Green)
-              << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
-              << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)
-              << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | ";
-            if(opt->no_skipped_summary == false) {
-                const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
-                s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped
-                  << " skipped" << Color::None;
-            }
-            s << "\n";
-            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6)
-              << p.numAsserts << " | "
-              << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
-              << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
-              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
-              << p.numAssertsFailed << " failed" << Color::None << " |\n";
-            s << Color::Cyan << "[doctest] " << Color::None
-              << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
-              << ((p.numTestCasesFailed > 0) ? "FAILURE!\n" : "SUCCESS!\n") << Color::None;
-        }
-
-        void test_case_start(const TestCaseData& in) override {
-            hasLoggedCurrentTestStart = false;
-            tc                        = &in;
-        }
-
-        void test_case_end(const CurrentTestCaseStats& st) override {
-            // log the preamble of the test case only if there is something
-            // else to print - something other than that an assert has failed
-            if(opt->duration ||
-               (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure))
-                logTestStart();
-
-            // report test case exceptions and crashes
-            bool crashed = st.failure_flags & TestCaseFailureReason::Crash;
-            if(crashed || (st.failure_flags & TestCaseFailureReason::Exception)) {
-                file_line_to_stream(tc->m_file, tc->m_line, " ");
-                successOrFailColoredStringToStream(false, crashed ? assertType::is_require :
-                                                                    assertType::is_check);
-                s << Color::Red << (crashed ? "test case CRASHED: " : "test case THREW exception: ")
-                  << Color::Cyan << st.error_string << "\n";
-
-                int num_stringified_contexts = get_num_stringified_contexts();
-                if(num_stringified_contexts) {
-                    auto stringified_contexts = get_stringified_contexts();
-                    s << Color::None << "  logged: ";
-                    for(int i = num_stringified_contexts - 1; i >= 0; --i) {
-                        s << (i == num_stringified_contexts - 1 ? "" : "          ")
-                          << stringified_contexts[i] << "\n";
-                    }
-                }
-                s << "\n";
-            }
-
-            // means the test case will be re-entered because there are untraversed (but discovered) subcases
-            if(st.should_reenter)
-                return;
-
-            if(opt->duration)
-                s << Color::None << std::setprecision(6) << std::fixed << st.seconds_so_far
-                  << " s: " << tc->m_name << "\n";
-
-            if(st.failure_flags & TestCaseFailureReason::Timeout)
-                s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)
-                  << std::fixed << tc->m_timeout << "!\n";
-
-            if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {
-                s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";
-            } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {
-                s << Color::Yellow << "Failed as expected so marking it as not failed\n";
-            } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {
-                s << Color::Yellow << "Allowed to fail so marking it as not failed\n";
-            } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {
-                s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures
-                  << " times so marking it as failed!\n";
-            } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {
-                s << Color::Yellow << "Failed exactly " << tc->m_expected_failures
-                  << " times as expected so marking it as not failed!\n";
-            }
-            if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {
-                s << Color::Red << "Aborting - too many failed asserts!\n";
-            }
-            s << Color::None;
-        }
-
-        void subcase_start(const SubcaseSignature& subc) override {
-            subcasesStack.push_back(subc);
-            hasLoggedCurrentTestStart = false;
-        }
-
-        void subcase_end(const SubcaseSignature& /*subc*/) override {
-            subcasesStack.pop_back();
-            hasLoggedCurrentTestStart = false;
-        }
-
-        void log_assert(const AssertData& rb) override {
-            if(!rb.m_failed && !opt->success)
-                return;
-
-            std::lock_guard<std::mutex> lock(g_mutex);
-
-            logTestStart();
-
-            file_line_to_stream(rb.m_file, rb.m_line, " ");
-            successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);
-            if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==
-               0) //!OCLINT bitwise operator in conditional
-                s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) "
-                  << Color::None;
-
-            if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
-                s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n";
-            } else if(rb.m_at &
-                      assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
-                s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", "
-                  << rb.m_exception_type << " ) " << Color::None
-                  << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" :
-                                                    "threw a DIFFERENT exception: ") :
-                                   "did NOT throw at all!")
-                  << Color::Cyan << rb.m_exception << "\n";
-            } else if(rb.m_at &
-                      assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
-                s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
-                  << rb.m_exception_type << "\" ) " << Color::None
-                  << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :
-                                                   "threw a DIFFERENT exception: ") :
-                                   "did NOT throw at all!")
-                  << Color::Cyan << rb.m_exception << "\n";
-            } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
-                s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan
-                  << rb.m_exception << "\n";
-            } else {
-                s << (rb.m_threw ? "THREW exception: " :
-                                   (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n"));
-                if(rb.m_threw)
-                    s << rb.m_exception << "\n";
-                else
-                    s << "  values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n";
-            }
-
-            log_contexts();
-        }
-
-        void log_message(const MessageData& mb) override {
-            std::lock_guard<std::mutex> lock(g_mutex);
-
-            logTestStart();
-
-            file_line_to_stream(mb.m_file, mb.m_line, " ");
-            s << getSuccessOrFailColor(false, mb.m_severity)
-              << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity,
-                                        "MESSAGE: ");
-            s << Color::None << mb.m_string << "\n";
-            log_contexts();
-        }
-
-        void test_case_skipped(const TestCaseData&) override {}
-    };
-
-    struct Whitespace
-    {
-        int nrSpaces;
-        explicit Whitespace(int nr)
-                : nrSpaces(nr) {}
-    };
-
-    std::ostream& operator<<(std::ostream& out, const Whitespace& ws) {
-        if(ws.nrSpaces != 0)
-            out << std::setw(ws.nrSpaces) << ' ';
-        return out;
-    }
-
-    // extension of the console reporter - with a bunch of helpers for the stdout stream redirection
-    struct ConsoleReporterWithHelpers : public ConsoleReporter
-    {
-        ConsoleReporterWithHelpers(std::ostream& in)
-                : ConsoleReporter(in) {}
-
         void printVersion() {
-            if(getContextOptions()->no_version == false)
+            if(opt.no_version == false)
                 s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
                   << DOCTEST_VERSION_STR << "\"\n";
         }
@@ -4538,6 +5096,8 @@ namespace {
               << Whitespace(sizePrefixDisplay*1) << "filters OUT subcases by their name\n";
             s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "r,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "reporters=<filters>           "
               << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n";
+            s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o,   --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out=<string>                  "
+              << Whitespace(sizePrefixDisplay*1) << "output filename\n";
             s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob,  --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string>             "
               << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";
             s << Whitespace(sizePrefixDisplay*3) << "                                       <string> - by [file/suite/name/rand]\n";
@@ -4600,13 +5160,13 @@ namespace {
                   << " name: " << curr.first.second << "\n";
         }
 
-        void output_query_results() {
+        void list_query_results() {
             separator_to_stream();
-            if(getContextOptions()->count || getContextOptions()->list_test_cases) {
+            if(opt.count || opt.list_test_cases) {
                 s << Color::Cyan << "[doctest] " << Color::None
                   << "unskipped test cases passing the current filters: "
                   << g_cs->numTestCasesPassingFilters << "\n";
-            } else if(getContextOptions()->list_test_suites) {
+            } else if(opt.list_test_suites) {
                 s << Color::Cyan << "[doctest] " << Color::None
                   << "unskipped test cases passing the current filters: "
                   << g_cs->numTestCasesPassingFilters << "\n";
@@ -4616,53 +5176,248 @@ namespace {
             }
         }
 
-        void output_query_preamble_test_cases() {
-            s << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n";
-            separator_to_stream();
+        // =========================================================================================
+        // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+        // =========================================================================================
+
+        void report_query(const QueryData& in) override {
+            if(opt.version) {
+                printVersion();
+            } else if(opt.help) {
+                printHelp();
+            } else if(opt.list_reporters) {
+                printRegisteredReporters();
+            } else if(opt.count || opt.list_test_cases) {
+                if(opt.list_test_cases) {
+                    s << Color::Cyan << "[doctest] " << Color::None
+                      << "listing all test case names\n";
+                    separator_to_stream();
+                }
+
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    s << Color::None << in.data[i] << "\n";
+
+                separator_to_stream();
+
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "unskipped test cases passing the current filters: "
+                  << g_cs->numTestCasesPassingFilters << "\n";
+
+            } else if(opt.list_test_suites) {
+                s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+                separator_to_stream();
+
+                for(unsigned i = 0; i < in.num_data; ++i)
+                    s << Color::None << in.data[i] << "\n";
+
+                separator_to_stream();
+
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "unskipped test cases passing the current filters: "
+                  << g_cs->numTestCasesPassingFilters << "\n";
+                s << Color::Cyan << "[doctest] " << Color::None
+                  << "test suites with unskipped test cases passing the current filters: "
+                  << g_cs->numTestSuitesPassingFilters << "\n";
+            }
         }
 
-        void output_query_preamble_test_suites() {
-            s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+        void test_run_start() override { printIntro(); }
+
+        void test_run_end(const TestRunStats& p) override {
             separator_to_stream();
+
+            const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0;
+            s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6)
+              << p.numTestCasesPassingFilters << " | "
+              << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None :
+                                                                          Color::Green)
+              << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed"
+              << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None)
+              << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | ";
+            if(opt.no_skipped_summary == false) {
+                const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters;
+                s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped
+                  << " skipped" << Color::None;
+            }
+            s << "\n";
+            s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6)
+              << p.numAsserts << " | "
+              << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green)
+              << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None
+              << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6)
+              << p.numAssertsFailed << " failed" << Color::None << " |\n";
+            s << Color::Cyan << "[doctest] " << Color::None
+              << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green)
+              << ((p.numTestCasesFailed > 0) ? "FAILURE!" : "SUCCESS!") << Color::None << std::endl;
         }
 
-        void output_c_string_with_newline(const char* str) { s << Color::None << str << "\n"; }
+        void test_case_start(const TestCaseData& in) override {
+            hasLoggedCurrentTestStart = false;
+            tc                        = &in;
+        }
+
+        void test_case_end(const CurrentTestCaseStats& st) override {
+            // log the preamble of the test case only if there is something
+            // else to print - something other than that an assert has failed
+            if(opt.duration ||
+               (st.failure_flags && st.failure_flags != TestCaseFailureReason::AssertFailure))
+                logTestStart();
+
+            if(opt.duration)
+                s << Color::None << std::setprecision(6) << std::fixed << st.seconds
+                  << " s: " << tc->m_name << "\n";
+
+            if(st.failure_flags & TestCaseFailureReason::Timeout)
+                s << Color::Red << "Test case exceeded time limit of " << std::setprecision(6)
+                  << std::fixed << tc->m_timeout << "!\n";
+
+            if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedButDidnt) {
+                s << Color::Red << "Should have failed but didn't! Marking it as failed!\n";
+            } else if(st.failure_flags & TestCaseFailureReason::ShouldHaveFailedAndDid) {
+                s << Color::Yellow << "Failed as expected so marking it as not failed\n";
+            } else if(st.failure_flags & TestCaseFailureReason::CouldHaveFailedAndDid) {
+                s << Color::Yellow << "Allowed to fail so marking it as not failed\n";
+            } else if(st.failure_flags & TestCaseFailureReason::DidntFailExactlyNumTimes) {
+                s << Color::Red << "Didn't fail exactly " << tc->m_expected_failures
+                  << " times so marking it as failed!\n";
+            } else if(st.failure_flags & TestCaseFailureReason::FailedExactlyNumTimes) {
+                s << Color::Yellow << "Failed exactly " << tc->m_expected_failures
+                  << " times as expected so marking it as not failed!\n";
+            }
+            if(st.failure_flags & TestCaseFailureReason::TooManyFailedAsserts) {
+                s << Color::Red << "Aborting - too many failed asserts!\n";
+            }
+            s << Color::None;
+        }
+
+        void test_case_exception(const TestCaseException& e) override {
+            logTestStart();
+
+            file_line_to_stream(s, tc->m_file, tc->m_line, " ");
+            successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require :
+                                                                   assertType::is_check);
+            s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ")
+              << Color::Cyan << e.error_string << "\n";
+
+            int num_stringified_contexts = get_num_stringified_contexts();
+            if(num_stringified_contexts) {
+                auto stringified_contexts = get_stringified_contexts();
+                s << Color::None << "  logged: ";
+                for(int i = num_stringified_contexts - 1; i >= 0; --i) {
+                    s << (i == num_stringified_contexts - 1 ? "" : "          ")
+                      << stringified_contexts[i] << "\n";
+                }
+            }
+            s << "\n" << Color::None;
+        }
+
+        void subcase_start(const SubcaseSignature& subc) override {
+            subcasesStack.push_back(subc);
+            hasLoggedCurrentTestStart = false;
+        }
+
+        void subcase_end() override {
+            subcasesStack.pop_back();
+            hasLoggedCurrentTestStart = false;
+        }
+
+        void log_assert(const AssertData& rb) override {
+            if(!rb.m_failed && !opt.success)
+                return;
+
+            std::lock_guard<std::mutex> lock(mutex);
+
+            logTestStart();
+
+            file_line_to_stream(s, rb.m_file, rb.m_line, " ");
+            successOrFailColoredStringToStream(!rb.m_failed, rb.m_at);
+            if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) ==
+               0) //!OCLINT bitwise operator in conditional
+                s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) "
+                  << Color::None;
+
+            if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
+                s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n";
+            } else if(rb.m_at &
+                      assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
+                s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", "
+                  << rb.m_exception_type << " ) " << Color::None
+                  << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" :
+                                                    "threw a DIFFERENT exception: ") :
+                                   "did NOT throw at all!")
+                  << Color::Cyan << rb.m_exception << "\n";
+            } else if(rb.m_at &
+                      assertType::is_throws_with) { //!OCLINT bitwise operator in conditional
+                s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \""
+                  << rb.m_exception_type << "\" ) " << Color::None
+                  << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" :
+                                                   "threw a DIFFERENT exception: ") :
+                                   "did NOT throw at all!")
+                  << Color::Cyan << rb.m_exception << "\n";
+            } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
+                s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan
+                  << rb.m_exception << "\n";
+            } else {
+                s << (rb.m_threw ? "THREW exception: " :
+                                   (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n"));
+                if(rb.m_threw)
+                    s << rb.m_exception << "\n";
+                else
+                    s << "  values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n";
+            }
+
+            log_contexts();
+        }
+
+        void log_message(const MessageData& mb) override {
+            std::lock_guard<std::mutex> lock(mutex);
+
+            logTestStart();
+
+            file_line_to_stream(s, mb.m_file, mb.m_line, " ");
+            s << getSuccessOrFailColor(false, mb.m_severity)
+              << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity,
+                                        "MESSAGE") << ": ";
+            s << Color::None << mb.m_string << "\n";
+            log_contexts();
+        }
+
+        void test_case_skipped(const TestCaseData&) override {}
     };
 
+    DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter);
+
 #ifdef DOCTEST_PLATFORM_WINDOWS
     struct DebugOutputWindowReporter : public ConsoleReporter
     {
         DOCTEST_THREAD_LOCAL static std::ostringstream oss;
 
-        DebugOutputWindowReporter()
-                : ConsoleReporter(oss) {}
+        DebugOutputWindowReporter(const ContextOptions& co)
+                : ConsoleReporter(co, oss) {}
 
-#define DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(func, type)                                  \
-    void func(type in) override {                                                                  \
-        if(isDebuggerActive()) {                                                                   \
-            bool with_col = g_no_colors;                                                           \
-            g_no_colors   = false;                                                                 \
-            ConsoleReporter::func(in);                                                             \
-            DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str());                                        \
-            oss.str("");                                                                           \
-            g_no_colors = with_col;                                                                \
-        }                                                                                          \
+#define DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(func, type, arg)                                    \
+    void func(type arg) override {                                                                 \
+        bool with_col = g_no_colors;                                                               \
+        g_no_colors   = false;                                                                     \
+        ConsoleReporter::func(arg);                                                                \
+        DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str());                                            \
+        oss.str("");                                                                               \
+        g_no_colors = with_col;                                                                    \
     }
 
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(test_run_start, const ContextOptions&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(test_run_end, const TestRunStats&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(test_case_start, const TestCaseData&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(subcase_end, const SubcaseSignature&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(log_assert, const AssertData&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(log_message, const MessageData&)
-        DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_start, DOCTEST_EMPTY, DOCTEST_EMPTY)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_run_end, const TestRunStats&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_start, const TestCaseData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_end, const CurrentTestCaseStats&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_exception, const TestCaseException&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_start, const SubcaseSignature&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(subcase_end, DOCTEST_EMPTY, DOCTEST_EMPTY)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_assert, const AssertData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(log_message, const MessageData&, in)
+        DOCTEST_DEBUG_OUTPUT_REPORTER_OVERRIDE(test_case_skipped, const TestCaseData&, in)
     };
 
     DOCTEST_THREAD_LOCAL std::ostringstream DebugOutputWindowReporter::oss;
-
-    DebugOutputWindowReporter g_debug_output_rep;
 #endif // DOCTEST_PLATFORM_WINDOWS
 
     // the implementation of parseFlag()
@@ -4787,7 +5542,7 @@ namespace {
             }
         } else {
             // integer
-            // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on unsuccessful parse...
+            // TODO: change this to use std::stoi or something else! currently it uses undefined behavior - assumes '0' on failed parse...
             int theInt = std::atoi(parsedValue.c_str()); // NOLINT
             if(theInt != 0) {
                 res = theInt; //!OCLINT parameter reassignment
@@ -4801,6 +5556,8 @@ namespace {
 Context::Context(int argc, const char* const* argv)
         : p(new detail::ContextState) {
     parseArgs(argc, argv, true);
+    if(argc)
+        p->binary_name = argv[0];
 }
 
 Context::~Context() {
@@ -4809,7 +5566,11 @@ Context::~Context() {
     delete p;
 }
 
-void Context::applyCommandLine(int argc, const char* const* argv) { parseArgs(argc, argv); }
+void Context::applyCommandLine(int argc, const char* const* argv) {
+    parseArgs(argc, argv);
+    if(argc)
+        p->binary_name = argv[0];
+}
 
 // parses args
 void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
@@ -4863,6 +5624,7 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
     p->var = strRes
 
     // clang-format off
+    DOCTEST_PARSE_STR_OPTION("out", "o", out, "");
     DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");
     DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);
 
@@ -4870,7 +5632,7 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
     DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX);
 
     DOCTEST_PARSE_INT_OPTION("abort-after", "aa", abort_after, 0);
-    DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, 2000000000);
+    DOCTEST_PARSE_INT_OPTION("subcase-filter-levels", "scfl", subcase_filter_levels, INT_MAX);
 
     DOCTEST_PARSE_AS_BOOL_OR_FLAG("success", "s", success, false);
     DOCTEST_PARSE_AS_BOOL_OR_FLAG("case-sensitive", "cs", case_sensitive, false);
@@ -4963,49 +5725,68 @@ void Context::setAssertHandler(detail::assert_handler ah) { p->ah = ah; }
 int Context::run() {
     using namespace detail;
 
-    auto old_cs        = g_cs;
+    // save the old context state in case such was setup - for using asserts out of a testing context
+    auto old_cs = g_cs;
+    // this is the current contest
     g_cs               = p;
     is_running_in_test = true;
-    g_no_colors        = p->no_colors;
+
+    g_no_colors = p->no_colors;
     p->resetRunData();
 
-    ConsoleReporterWithHelpers g_con_rep(std::cout);
-    registerReporter("console", 0, g_con_rep);
+    // stdout by default
+    p->cout = &std::cout;
+    p->cerr = &std::cerr;
+
+    // or to a file if specified
+    std::fstream fstr;
+    if(p->out.size()) {
+        fstr.open(p->out.c_str(), std::fstream::out);
+        p->cout = &fstr;
+    }
+
+    auto cleanup_and_return = [&]() {
+        if(fstr.is_open())
+            fstr.close();
+
+        // restore context
+        g_cs               = old_cs;
+        is_running_in_test = false;
+
+        // we have to free the reporters which were allocated when the run started
+        for(auto& curr : p->reporters_currently_used)
+            delete curr;
+        p->reporters_currently_used.clear();
+
+        if(p->numTestCasesFailed && !p->no_exitcode)
+            return EXIT_FAILURE;
+        return EXIT_SUCCESS;
+    };
+
+    DOCTEST_REGISTER_REPORTER("console", 0, ConsoleReporter);
 
     // setup default reporter if none is given through the command line
-    p->reporters_currently_used.clear();
     if(p->filters[8].empty())
-        p->reporters_currently_used.push_back(getReporters()[reporterMap::key_type(0, "console")]);
+        p->filters[8].push_back("console");
 
     // check to see if any of the registered reporters has been selected
     for(auto& curr : getReporters()) {
         if(matchesAny(curr.first.second.c_str(), p->filters[8], false, p->case_sensitive))
-            p->reporters_currently_used.push_back(curr.second);
+            p->reporters_currently_used.push_back(curr.second(*g_cs));
     }
 
-    // always use the debug output window reporter
 #ifdef DOCTEST_PLATFORM_WINDOWS
     if(isDebuggerActive())
-        p->reporters_currently_used.push_back(&g_debug_output_rep);
+        p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs));
 #endif // DOCTEST_PLATFORM_WINDOWS
 
     // handle version, help and no_run
     if(p->no_run || p->version || p->help || p->list_reporters) {
-        if(p->version)
-            g_con_rep.printVersion();
-        if(p->help)
-            g_con_rep.printHelp();
-        if(p->list_reporters)
-            g_con_rep.printRegisteredReporters();
+        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, QueryData());
 
-        g_cs               = old_cs;
-        is_running_in_test = false;
-
-        return EXIT_SUCCESS;
+        return cleanup_and_return();
     }
 
-    g_con_rep.printIntro();
-
     std::vector<const TestCase*> testArray;
     for(auto& curr : getRegisteredTests())
         testArray.push_back(&curr);
@@ -5014,11 +5795,11 @@ int Context::run() {
     // sort the collected records
     if(!testArray.empty()) {
         if(p->order_by.compare("file", true) == 0) {
-            std::qsort(&testArray[0], testArray.size(), sizeof(TestCase*), fileOrderComparator);
+            std::sort(testArray.begin(), testArray.end(), fileOrderComparator);
         } else if(p->order_by.compare("suite", true) == 0) {
-            std::qsort(&testArray[0], testArray.size(), sizeof(TestCase*), suiteOrderComparator);
+            std::sort(testArray.begin(), testArray.end(), suiteOrderComparator);
         } else if(p->order_by.compare("name", true) == 0) {
-            std::qsort(&testArray[0], testArray.size(), sizeof(TestCase*), nameOrderComparator);
+            std::sort(testArray.begin(), testArray.end(), nameOrderComparator);
         } else if(p->order_by.compare("rand", true) == 0) {
             std::srand(p->rand_seed);
 
@@ -5035,21 +5816,17 @@ int Context::run() {
         }
     }
 
-    if(p->list_test_cases)
-        g_con_rep.output_query_preamble_test_cases();
-
     std::set<String> testSuitesPassingFilt;
-    if(p->list_test_suites)
-        g_con_rep.output_query_preamble_test_suites();
 
-    bool query_mode = p->count || p->list_test_cases || p->list_test_suites;
+    bool                query_mode = p->count || p->list_test_cases || p->list_test_suites;
+    std::vector<String> queryResults;
 
     if(!query_mode)
-        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, *g_cs);
+        DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, DOCTEST_EMPTY);
 
     // invoke the registered functions if they match the filter criteria (or just count them)
     for(auto& curr : testArray) {
-        const auto tc = *curr;
+        const auto& tc = *curr;
 
         bool skip_me = false;
         if(tc.m_skip && !p->no_skip)
@@ -5088,14 +5865,14 @@ int Context::run() {
 
         // print the name of the test and don't execute it
         if(p->list_test_cases) {
-            g_con_rep.output_c_string_with_newline(tc.m_name);
+            queryResults.push_back(tc.m_name);
             continue;
         }
 
         // print the name of the test suite if not done already and don't execute it
         if(p->list_test_suites) {
             if((testSuitesPassingFilt.count(tc.m_test_suite) == 0) && tc.m_test_suite[0] != '\0') {
-                g_con_rep.output_c_string_with_newline(tc.m_test_suite);
+                queryResults.push_back(tc.m_test_suite);
                 testSuitesPassingFilt.insert(tc.m_test_suite);
                 p->numTestSuitesPassingFilters++;
             }
@@ -5106,15 +5883,19 @@ int Context::run() {
         {
             p->currentTest = &tc;
 
-            p->failure_flags  = TestCaseFailureReason::None;
-            p->seconds_so_far = 0;
-            p->error_string   = "";
+            p->failure_flags = TestCaseFailureReason::None;
+            p->seconds       = 0;
 
-            // reset non-atomic counters
-            p->numAssertsFailedForCurrentTestCase = 0;
-            p->numAssertsForCurrentTestCase       = 0;
+            // reset atomic counters
+            p->numAssertsFailedCurrentTest_atomic = 0;
+            p->numAssertsCurrentTest_atomic       = 0;
 
             p->subcasesPassed.clear();
+
+            DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
+
+            p->timer.start();
+
             do {
                 // reset some of the fields for subcases (except for the set of fully passed ones)
                 p->should_reenter       = false;
@@ -5124,14 +5905,6 @@ int Context::run() {
                 // reset stuff for logging with INFO()
                 p->stringifiedContexts.clear();
 
-                // reset atomic counters
-                p->numAssertsFailedForCurrentTestCase_atomic = 0;
-                p->numAssertsForCurrentTestCase_atomic       = 0;
-
-                DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
-
-                g_timer.start();
-
 #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
                 try {
 #endif // DOCTEST_CONFIG_NO_EXCEPTIONS
@@ -5143,66 +5916,21 @@ int Context::run() {
                 } catch(const TestFailureException&) {
                     p->failure_flags |= TestCaseFailureReason::AssertFailure;
                 } catch(...) {
-                    p->error_string = translateActiveException();
+                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_exception,
+                                                      {translateActiveException(), false});
                     p->failure_flags |= TestCaseFailureReason::Exception;
                 }
 #endif // DOCTEST_CONFIG_NO_EXCEPTIONS
 
-                p->seconds_so_far += g_timer.getElapsedSeconds();
-
-                // update the non-atomic counters
-                p->numAsserts += p->numAssertsForCurrentTestCase_atomic;
-                p->numAssertsForCurrentTestCase += p->numAssertsForCurrentTestCase_atomic;
-                p->numAssertsFailed += p->numAssertsFailedForCurrentTestCase_atomic;
-                p->numAssertsFailedForCurrentTestCase +=
-                        p->numAssertsFailedForCurrentTestCase_atomic;
-
                 // exit this loop if enough assertions have failed - even if there are more subcases
-                if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) {
+                if(p->abort_after > 0 &&
+                   p->numAssertsFailed + p->numAssertsFailedCurrentTest_atomic >= p->abort_after) {
                     p->should_reenter = false;
                     p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
                 }
-
-                // call it from here only if we will continue looping for other subcases and
-                // call it again outside of the loop for one final time - with updated flags
-                if(p->should_reenter == true) {
-                    DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
-                    // remove these flags - it is expected that the reporters have handled these issues
-                    p->failure_flags &= ~TestCaseFailureReason::Exception;
-                    p->failure_flags &= ~TestCaseFailureReason::AssertFailure;
-                }
             } while(p->should_reenter == true);
 
-            if(p->numAssertsFailedForCurrentTestCase)
-                p->failure_flags |= TestCaseFailureReason::AssertFailure;
-
-            if(Approx(p->currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
-               Approx(p->seconds_so_far).epsilon(DBL_EPSILON) > p->currentTest->m_timeout)
-                p->failure_flags |= TestCaseFailureReason::Timeout;
-
-            if(tc.m_should_fail) {
-                if(p->failure_flags) {
-                    p->failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
-                } else {
-                    p->failure_flags |= TestCaseFailureReason::ShouldHaveFailedButDidnt;
-                }
-            } else if(p->failure_flags && tc.m_may_fail) {
-                p->failure_flags |= TestCaseFailureReason::CouldHaveFailedAndDid;
-            } else if(tc.m_expected_failures > 0) {
-                if(p->numAssertsFailedForCurrentTestCase == tc.m_expected_failures) {
-                    p->failure_flags |= TestCaseFailureReason::FailedExactlyNumTimes;
-                } else {
-                    p->failure_flags |= TestCaseFailureReason::DidntFailExactlyNumTimes;
-                }
-            }
-
-            bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & p->failure_flags) ||
-                              (TestCaseFailureReason::CouldHaveFailedAndDid & p->failure_flags) ||
-                              (TestCaseFailureReason::FailedExactlyNumTimes & p->failure_flags);
-
-            // if any subcase has failed - the whole test case has failed
-            if(p->failure_flags && !ok_to_fail)
-                p->numTestCasesFailed++;
+            p->finalizeTestCaseData();
 
             DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_cs);
 
@@ -5214,17 +5942,17 @@ int Context::run() {
         }
     }
 
-    if(!query_mode)
+    if(!query_mode) {
         DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_cs);
-    else
-        g_con_rep.output_query_results();
+    } else {
+        QueryData qdata;
+        qdata.run_stats = g_cs;
+        qdata.data      = queryResults.data();
+        qdata.num_data  = unsigned(queryResults.size());
+        DOCTEST_ITERATE_THROUGH_REPORTERS(report_query, qdata);
+    }
 
-    g_cs               = old_cs;
-    is_running_in_test = false;
-
-    if(p->numTestCasesFailed && !p->no_exitcode)
-        return EXIT_FAILURE;
-    return EXIT_SUCCESS;
+    return cleanup_and_return();
 }
 
 DOCTEST_DEFINE_DEFAULTS(CurrentTestCaseStats);
@@ -5243,10 +5971,11 @@ const String* IReporter::get_stringified_contexts() {
     return get_num_stringified_contexts() ? &detail::g_cs->stringifiedContexts[0] : nullptr;
 }
 
-int registerReporter(const char* name, int priority, IReporter& r) {
-    getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), &r));
-    return 0;
-}
+namespace detail {
+    void registerReporterImpl(const char* name, int priority, reporterCreatorFunc c) {
+        getReporters().insert(reporterMap::value_type(reporterMap::key_type(priority, name), c));
+    }
+} // namespace detail
 
 // see these issues on the reasoning for this:
 // - https://github.com/onqtam/doctest/issues/143#issuecomment-414418903
@@ -5258,7 +5987,9 @@ void DOCTEST_FIX_FOR_MACOS_LIBCPP_IOSFWD_STRING_LINK_ERRORS() { std::cout << std
 #endif // DOCTEST_CONFIG_DISABLE
 
 #ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4007) // 'function' : must be 'attribute' - see issue #182
 int main(int argc, char** argv) { return doctest::Context(argc, argv).run(); }
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
 #endif // DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
 
 DOCTEST_CLANG_SUPPRESS_WARNING_POP
@@ -5266,4 +5997,4 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP
 DOCTEST_GCC_SUPPRESS_WARNING_POP
 
 #endif // DOCTEST_LIBRARY_IMPLEMENTATION
-#endif // DOCTEST_CONFIG_IMPLEMENT
+#endif // DOCTEST_CONFIG_IMPLEMENT
\ No newline at end of file