From ff51a32be1b1d113d4e7d71139e33e5b8ea243d2 Mon Sep 17 00:00:00 2001 From: onqtam 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 +#include +#else // DOCTEST_CONFIG_USE_STD_HEADERS + #if DOCTEST_CLANG // to detect if libc++ is being used with clang (the _LIBCPP_VERSION identifier) #include #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 header is used - also it is very light and doesn't drag a ton of stuff -#include -#else // _LIBCPP_VERSION -namespace std { +DOCTEST_STD_NAMESPACE_BEGIN +typedef decltype(nullptr) nullptr_t; template struct char_traits; template <> struct char_traits; template class basic_ostream; -typedef basic_ostream > ostream; -} // namespace std -#endif // _LIBCPP_VERSION || DOCTEST_CONFIG_USE_IOSFWD - -#ifdef _LIBCPP_VERSION -#include -#else // _LIBCPP_VERSION -namespace std { -typedef decltype(nullptr) nullptr_t; -} -#endif // _LIBCPP_VERSION +typedef basic_ostream> ostream; +template +class tuple; +DOCTEST_STD_NAMESPACE_END DOCTEST_MSVC_SUPPRESS_WARNING_POP +#endif // DOCTEST_CONFIG_USE_STD_HEADERS + #ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #include #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 \ 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 Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { return Expression_lhs(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 struct StringStreamBase { template 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 + IReporter* reporterCreator(const ContextOptions& o) { + return new Reporter(o); + } +} // namespace detail + +template +int registerReporter(const char* name, int priority) { + detail::registerReporterImpl(name, priority, detail::reporterCreator); + 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(), 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 \ - inline void anon(); \ + inline void func(); \ + template \ + struct iter; \ template \ - struct DOCTEST_CAT(id, ITERATOR) \ + struct iter> \ { \ - DOCTEST_CAT(id, ITERATOR)(int line, int index) { \ - DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(anon, Type, dec, line * 1000 + index); \ - DOCTEST_CAT(id, ITERATOR)(line, index + 1); \ + iter(int line, int index) { \ + DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(func, Type, dec, line * 1000 + index); \ + iter>(line, index + 1); \ } \ }; \ - template \ - struct DOCTEST_CAT(id, ITERATOR) \ + template <> \ + struct iter> \ { \ - DOCTEST_CAT(id, ITERATOR)(int line, int index) { \ - DOCTEST_REGISTER_TYPED_TEST_CASE_IMPL(anon, 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 \ - 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)> 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 \ 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(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 \ 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 #include #include +#include #include #include #include @@ -2686,7 +2778,9 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include #include #include +#ifdef DOCTEST_CONFIG_POSIX_SIGNALS #include +#endif // DOCTEST_CONFIG_POSIX_SIGNALS #include #include #include @@ -2697,6 +2791,34 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include #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 +#else +#include +#endif +#include + +#else // DOCTEST_PLATFORM_WINDOWS + +#include +#include + +#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(&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(&hz)); + QueryPerformanceCounter(reinterpret_cast(&hzo)); + } + UInt64 t; + QueryPerformanceCounter(reinterpret_cast(&t)); + return ((t - hzo) * 1000000) / hz; + } +#else // DOCTEST_PLATFORM_WINDOWS + UInt64 getCurrentTicks() { + timeval t; + gettimeofday(&t, nullptr); + return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); + } +#endif // DOCTEST_PLATFORM_WINDOWS + + struct Timer + { + void start() { m_ticks = getCurrentTicks(); } + unsigned int getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + //unsigned int getElapsedMilliseconds() const { + // return static_cast(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 numAssertsForCurrentTestCase_atomic; - std::atomic numAssertsFailedForCurrentTestCase_atomic; + std::atomic numAssertsCurrentTest_atomic; + std::atomic numAssertsFailedCurrentTest_atomic; - std::vector > filters = decltype(filters)(9); // 9 different filters + std::vector> filters = decltype(filters)(9); // 9 different filters std::vector reporters_currently_used; @@ -2820,12 +2979,15 @@ namespace detail { assert_handler ah = nullptr; + Timer timer; + std::vector stringifiedContexts; // logging from INFO() due to an exception // stuff for subcases std::set subcasesPassed; std::set 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 -#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 -#else -#include -#endif -#include - -DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END - -#else // DOCTEST_PLATFORM_WINDOWS - -#include - -#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, 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, 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(&hz)); - QueryPerformanceCounter(reinterpret_cast(&hzo)); - } - UInt64 t; - QueryPerformanceCounter(reinterpret_cast(&t)); - return ((t - hzo) * 1000000) / hz; - } -#else // DOCTEST_PLATFORM_WINDOWS - - typedef uint64_t UInt64; - - UInt64 getCurrentTicks() { - timeval t; - gettimeofday(&t, nullptr); - return static_cast(t.tv_sec) * 1000000 + static_cast(t.tv_usec); - } -#endif // DOCTEST_PLATFORM_WINDOWS - - struct Timer - { - void start() { m_ticks = getCurrentTicks(); } - unsigned int getElapsedMicroseconds() const { - return static_cast(getCurrentTicks() - m_ticks); - } - //unsigned int getElapsedMilliseconds() const { - // return static_cast(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(a); - auto rhs = *static_cast(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(lhs->m_line) - static_cast(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(a); - auto rhs = *static_cast(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(a); - auto rhs = *static_cast(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 + [[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 + 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 + 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 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(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 << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + 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_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 << ""; + // m_needsNewline = true; + // return *this; + //} + + //void XmlWriter::writeStylesheetRef( std::string const& url ) { + // m_os << "\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 << "\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 = ∈ + 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 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 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 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 = ∈ - } - - 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 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 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= " << Whitespace(sizePrefixDisplay*1) << "reporters to use (console is default)\n"; + s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "o, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "out= " + << Whitespace(sizePrefixDisplay*1) << "output filename\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; s << Whitespace(sizePrefixDisplay*3) << " - 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 = ∈ + } + + 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 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 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 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 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 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