initial version of a reporter system! inherit from the IReporter class and register it with doctest::registerReporter()
diff --git a/.clang-format b/.clang-format
index 6b1cb1d..0dee6af 100644
--- a/.clang-format
+++ b/.clang-format
@@ -21,6 +21,7 @@
IndentCaseLabels: true
ContinuationIndentWidth: 8
NamespaceIndentation: Inner
+FixNamespaceComments: true
AccessModifierOffset: -4
SpaceAfterControlStatementKeyword: false
diff --git a/doc/markdown/commandline.md b/doc/markdown/commandline.md
index 8a64069..3bc4bba 100644
--- a/doc/markdown/commandline.md
+++ b/doc/markdown/commandline.md
@@ -20,6 +20,7 @@
| ```-c``` ```--count``` | Prints the number of test cases matching the current filters (see below) |
| ```-ltc``` ```--list-test-cases``` | Lists all test cases by name which match the current filters (see below) |
| ```-lts``` ```--list-test-suites``` | Lists all test suites by name which have at least one test case matching the current filters (see below) |
+| ```-lr``` ```--list-reporters``` | Lists all registered [**reporters**](reporters.md) |
| **Int/String Options** | <hr> |
| ```-tc``` ```--test-case=<filters>``` | Filters test cases based on their name. By default all test cases match but if a value is given to this filter like ```--test-case=*math*,*sound*``` then only test cases who match atleast one of the patterns in the comma-separated list with wildcards will get executed/counted/listed |
| ```-tce``` ```--test-case-exclude=<filters>``` | Same as the ```-test-case=<filters>``` option but if any of the patterns in the comma-separated list of values matches - then the test case is skipped |
@@ -29,6 +30,7 @@
| ```-tse``` ```--test-suite-exclude=<filters>``` | Same as ```--test-case-exclude=<filters>``` but filters based on the test suite in which test cases are in |
| ```-sc``` ```--subcase=<filters>``` | Same as ```--test-case=<filters>``` but filters subcases based on their names |
| ```-sce``` ```--subcase-exclude=<filters>``` | Same as ```--test-case-exclude=<filters>``` but filters based on subcase names |
+| ```-r``` ```--reporters=<filters>``` | List of [**reporters**](reporters.md) to use (default is ```console```) |
| ```-ob``` ```--order-by=<string>``` | Test cases will be sorted before being executed either by **the file in which they are** / **the test suite they are in** / **their name** / **random**. The possible values of ```<string>``` are ```file```/```suite```/```name```/```rand```. The default is ```file``` |
| ```-rs``` ```--rand-seed=<int>``` | The seed for random ordering |
| ```-f``` ```--first=<int>``` | The **first** test case to execute which passes the current filters - for range-based execution - see [**the example python script**](../../examples/range_based_execution.py) |
diff --git a/doc/markdown/features.md b/doc/markdown/features.md
index 0064437..0aedd0f 100644
--- a/doc/markdown/features.md
+++ b/doc/markdown/features.md
@@ -45,6 +45,7 @@
- [**templated test cases**](parameterized-tests.md#templated-test-cases---parameterized-by-type) - parameterized by type
- supports [**logging macros**](logging.md) for capturing local variables and strings - as a message for when an assert fails - with lazy stringification and no allocations when possible!
- crash handling support - uses signals for UNIX and SEH for Windows
+- an extensible [**reporter system**](reporters.md) (can be also used for implementing event listeners)
- output from all compilers on all platforms is the same - byte by byte
- binaries (exe/dll) can use the test runner of another binary - so tests end up in a single registry - [**example**](../../examples/executable_dll_and_plugin/)
- supports [**BDD style**](testcases.md) tests
diff --git a/doc/markdown/reporters.md b/doc/markdown/reporters.md
new file mode 100644
index 0000000..8f75714
--- /dev/null
+++ b/doc/markdown/reporters.md
@@ -0,0 +1,7 @@
+## Reporters
+
+TODO: document me!
+
+---------------
+
+[Home](readme.md#reference)
diff --git a/doc/markdown/roadmap.md b/doc/markdown/roadmap.md
index e4b089b..24d3c46 100644
--- a/doc/markdown/roadmap.md
+++ b/doc/markdown/roadmap.md
@@ -90,6 +90,7 @@
- update traits - use declval, etc.
- move initialization of fields from initializer lists to class bodies
- update static code analysis - less warning suppressing
+- drop some config options - simplify!!!
### For 3.0:
diff --git a/doctest/doctest.h b/doctest/doctest.h
index e0cc432..323a53f 100644
--- a/doctest/doctest.h
+++ b/doctest/doctest.h
@@ -210,6 +210,11 @@
DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted
DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
+// static analysis
+DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
+DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
+DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
+DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr...
// C4548 - expression before comma has no effect; expected expression with side - effect
// C4986 - exception specification does not match previous declaration
@@ -266,8 +271,12 @@
#ifndef DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
+#ifndef DOCTEST_CONFIG_WITH_OVERRIDE
+#define DOCTEST_CONFIG_WITH_OVERRIDE
+#endif // DOCTEST_CONFIG_WITH_OVERRIDE
#endif // __cplusplus >= 201103L
+// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support
// 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:
@@ -469,6 +478,12 @@
#define DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK 5
#endif // DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK
+#ifdef DOCTEST_CONFIG_WITH_OVERRIDE
+#define DOCTEST_OVERRIDE override
+#else // DOCTEST_CONFIG_WITH_OVERRIDE
+#define DOCTEST_OVERRIDE
+#endif // DOCTEST_CONFIG_WITH_OVERRIDE
+
// =================================================================================================
// == FEATURE DETECTION END ========================================================================
// =================================================================================================
@@ -519,6 +534,12 @@
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") static int var DOCTEST_UNUSED
#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#ifdef DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled
+#else // DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled
+#endif // DOCTEST_CONFIG_DISABLE
+
// should probably take a look at https://github.com/scottt/debugbreak
#ifdef DOCTEST_PLATFORM_MAC
#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
@@ -536,12 +557,11 @@
#include <ciso646>
#endif // clang
-#ifdef _LIBCPP_VERSION
+#if defined(_LIBCPP_VERSION) || defined(DOCTEST_CONFIG_USE_IOSFWD)
// not forward declaring ostream for libc++ because I had some problems (inline namespaces vs c++98)
// so the <iosfwd> header is used - also it is very light and doesn't drag a ton of stuff
#include <iosfwd>
-#else // _LIBCPP_VERSION
-#ifndef DOCTEST_CONFIG_USE_IOSFWD
+#else // _LIBCPP_VERSION
namespace std
{
template <class charT>
@@ -552,10 +572,7 @@
class basic_ostream;
typedef basic_ostream<char, char_traits<char> > ostream;
} // namespace std
-#else // DOCTEST_CONFIG_USE_IOSFWD
-#include <iosfwd>
-#endif // DOCTEST_CONFIG_USE_IOSFWD
-#endif // _LIBCPP_VERSION
+#endif // _LIBCPP_VERSION || DOCTEST_CONFIG_USE_IOSFWD
// static assert macro - because of the c++98 support requires that the message is an
// identifier (no spaces and not a C string) - example without quotes: I_am_a_message
@@ -587,8 +604,6 @@
#endif // _LIBCPP_VERSION
#endif // DOCTEST_CONFIG_WITH_NULLPTR
-#ifndef DOCTEST_CONFIG_DISABLE
-
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#include <type_traits>
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
@@ -597,48 +612,10 @@
{
namespace detail
{
- struct TestSuite
- {
- const char* m_test_suite;
- const char* m_description;
- bool m_skip;
- bool m_may_fail;
- bool m_should_fail;
- int m_expected_failures;
- double m_timeout;
-
- TestSuite& operator*(const char* in) {
- m_test_suite = in;
- // clear state
- m_description = 0;
- m_skip = false;
- m_may_fail = false;
- m_should_fail = false;
- m_expected_failures = 0;
- m_timeout = 0;
- return *this;
- }
-
- template <typename T>
- TestSuite& operator*(const T& in) {
- in.fill(*this);
- return *this;
- }
- };
+ // the function type this library works with
+ typedef void (*funcType)();
} // namespace detail
-} // namespace doctest
-// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
-// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
-namespace doctest_detail_test_suite_ns
-{
-DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
-} // namespace doctest_detail_test_suite_ns
-
-#endif // DOCTEST_CONFIG_DISABLE
-
-namespace doctest
-{
// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length
// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:
// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)
@@ -763,6 +740,251 @@
DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
+namespace Color
+{
+ enum Enum
+ {
+ None = 0,
+ White,
+ Red,
+ Green,
+ Blue,
+ Cyan,
+ Yellow,
+ Grey,
+
+ Bright = 0x10,
+
+ BrightRed = Bright | Red,
+ BrightGreen = Bright | Green,
+ LightGrey = Bright | Grey,
+ BrightWhite = Bright | White
+ };
+
+ DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code);
+} // namespace Color
+
+namespace assertType
+{
+ enum Enum
+ {
+ // macro traits
+
+ is_warn = 1,
+ is_check = 2,
+ is_require = 4,
+
+ is_throws = 8,
+ is_throws_as = 16,
+ is_nothrow = 32,
+
+ is_fast = 64, // not checked anywhere - used just to distinguish the types
+ is_false = 128,
+ is_unary = 256,
+
+ is_eq = 512,
+ is_ne = 1024,
+
+ is_lt = 2048,
+ is_gt = 4096,
+
+ is_ge = 8192,
+ is_le = 16384,
+
+ // macro types
+
+ DT_WARN = is_warn,
+ DT_CHECK = is_check,
+ DT_REQUIRE = is_require,
+
+ DT_WARN_FALSE = is_false | is_warn,
+ DT_CHECK_FALSE = is_false | is_check,
+ DT_REQUIRE_FALSE = is_false | is_require,
+
+ DT_WARN_THROWS = is_throws | is_warn,
+ DT_CHECK_THROWS = is_throws | is_check,
+ DT_REQUIRE_THROWS = is_throws | is_require,
+
+ DT_WARN_THROWS_AS = is_throws_as | is_warn,
+ DT_CHECK_THROWS_AS = is_throws_as | is_check,
+ DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
+
+ DT_WARN_NOTHROW = is_nothrow | is_warn,
+ DT_CHECK_NOTHROW = is_nothrow | is_check,
+ DT_REQUIRE_NOTHROW = is_nothrow | is_require,
+
+ DT_WARN_EQ = is_eq | is_warn,
+ DT_CHECK_EQ = is_eq | is_check,
+ DT_REQUIRE_EQ = is_eq | is_require,
+
+ DT_WARN_NE = is_ne | is_warn,
+ DT_CHECK_NE = is_ne | is_check,
+ DT_REQUIRE_NE = is_ne | is_require,
+
+ DT_WARN_GT = is_gt | is_warn,
+ DT_CHECK_GT = is_gt | is_check,
+ DT_REQUIRE_GT = is_gt | is_require,
+
+ DT_WARN_LT = is_lt | is_warn,
+ DT_CHECK_LT = is_lt | is_check,
+ DT_REQUIRE_LT = is_lt | is_require,
+
+ DT_WARN_GE = is_ge | is_warn,
+ DT_CHECK_GE = is_ge | is_check,
+ DT_REQUIRE_GE = is_ge | is_require,
+
+ DT_WARN_LE = is_le | is_warn,
+ DT_CHECK_LE = is_le | is_check,
+ DT_REQUIRE_LE = is_le | is_require,
+
+ DT_WARN_UNARY = is_unary | is_warn,
+ DT_CHECK_UNARY = is_unary | is_check,
+ DT_REQUIRE_UNARY = is_unary | is_require,
+
+ DT_WARN_UNARY_FALSE = is_false | is_unary | is_warn,
+ DT_CHECK_UNARY_FALSE = is_false | is_unary | is_check,
+ DT_REQUIRE_UNARY_FALSE = is_false | is_unary | is_require,
+
+ DT_FAST_WARN_EQ = is_fast | is_eq | is_warn,
+ DT_FAST_CHECK_EQ = is_fast | is_eq | is_check,
+ DT_FAST_REQUIRE_EQ = is_fast | is_eq | is_require,
+
+ DT_FAST_WARN_NE = is_fast | is_ne | is_warn,
+ DT_FAST_CHECK_NE = is_fast | is_ne | is_check,
+ DT_FAST_REQUIRE_NE = is_fast | is_ne | is_require,
+
+ DT_FAST_WARN_GT = is_fast | is_gt | is_warn,
+ DT_FAST_CHECK_GT = is_fast | is_gt | is_check,
+ DT_FAST_REQUIRE_GT = is_fast | is_gt | is_require,
+
+ DT_FAST_WARN_LT = is_fast | is_lt | is_warn,
+ DT_FAST_CHECK_LT = is_fast | is_lt | is_check,
+ DT_FAST_REQUIRE_LT = is_fast | is_lt | is_require,
+
+ DT_FAST_WARN_GE = is_fast | is_ge | is_warn,
+ DT_FAST_CHECK_GE = is_fast | is_ge | is_check,
+ DT_FAST_REQUIRE_GE = is_fast | is_ge | is_require,
+
+ DT_FAST_WARN_LE = is_fast | is_le | is_warn,
+ DT_FAST_CHECK_LE = is_fast | is_le | is_check,
+ DT_FAST_REQUIRE_LE = is_fast | is_le | is_require,
+
+ DT_FAST_WARN_UNARY = is_fast | is_unary | is_warn,
+ DT_FAST_CHECK_UNARY = is_fast | is_unary | is_check,
+ DT_FAST_REQUIRE_UNARY = is_fast | is_unary | is_require,
+
+ DT_FAST_WARN_UNARY_FALSE = is_fast | is_false | is_unary | is_warn,
+ DT_FAST_CHECK_UNARY_FALSE = is_fast | is_false | is_unary | is_check,
+ DT_FAST_REQUIRE_UNARY_FALSE = is_fast | is_false | is_unary | is_require
+ };
+} // namespace assertType
+
+DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
+
+struct TestCaseData
+{
+ const char* m_file; // the file in which the test was registered
+ unsigned m_line; // the line where the test was registered
+ const char* m_name; // name of the test case
+ const char* m_test_suite; // the test suite in which the test was added
+ const char* m_description;
+ bool m_skip;
+ bool m_may_fail;
+ bool m_should_fail;
+ int m_expected_failures;
+ double m_timeout;
+};
+
+struct AssertData
+{
+ // common - for all asserts
+ const TestCaseData* m_test_case;
+ assertType::Enum m_at;
+ const char* m_file;
+ int m_line;
+ const char* m_expr;
+ bool m_failed;
+
+ // exception-related - for all asserts
+ bool m_threw;
+ String m_exception;
+
+ // for normal asserts
+ String m_decomposition;
+
+ // for specific exception-related asserts
+ bool m_threw_as;
+ const char* m_exception_type;
+};
+
+struct MessageData
+{
+ String m_string;
+ const char* m_file;
+ int m_line;
+ assertType::Enum m_severity;
+
+ // for gcc 4.8
+ DOCTEST_NOINLINE ~MessageData() {}
+};
+
+struct DOCTEST_INTERFACE SubcaseSignature
+{
+ const char* m_name;
+ const char* m_file;
+ int m_line;
+
+ SubcaseSignature(const char* name, const char* file, int line)
+ : m_name(name)
+ , m_file(file)
+ , m_line(line) {}
+
+ bool operator<(const SubcaseSignature& other) const;
+};
+
+struct IContextScope
+{
+ virtual ~IContextScope() {}
+ virtual void stringify(std::ostream*) const = 0;
+};
+
+struct ContextOptions //!OCLINT too many fields
+{
+ // == parameters from the command line
+ String order_by; // how tests should be ordered
+ unsigned rand_seed; // the seed for rand ordering
+
+ unsigned first; // the first (matching) test to be executed
+ unsigned last; // the last (matching) test to be executed
+
+ int abort_after; // stop tests after this many failed assertions
+ int subcase_filter_levels; // apply the subcase filters for the first N levels
+
+ bool success; // include successful assertions in output
+ bool case_sensitive; // if filtering should be case sensitive
+ bool exit; // if the program should be exited after the tests are ran/whatever
+ bool duration; // print the time duration of each test case
+ bool no_throw; // to skip exceptions-related assertion macros
+ bool no_exitcode; // if the framework should return 0 as the exitcode
+ bool no_run; // to not run the tests at all (can be done with an "*" exclude)
+ bool no_version; // to not print the version of the framework
+ bool no_colors; // if output to the console should be colorized
+ bool force_colors; // forces the use of colors even when a tty cannot be detected
+ bool no_breaks; // to not break into the debugger
+ bool no_skip; // don't skip test cases which are marked to be skipped
+ bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x):
+ bool no_path_in_filenames; // if the path to files should be removed from the output
+ bool no_line_numbers; // if source code line numbers should be omitted from the output
+ bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
+
+ bool help; // to print the help
+ bool version; // to print the version
+ bool count; // if only the count of matching tests is to be retreived
+ bool list_test_cases; // to list all tests matching the filters
+ bool list_test_suites; // to list all suites matching the filters
+ bool list_reporters; // lists all registered reporters
+};
+
namespace detail
{
#ifndef DOCTEST_CONFIG_WITH_STATIC_ASSERT
@@ -1129,126 +1351,6 @@
namespace detail
{
- // the function type this library works with
- typedef void (*funcType)();
-
- namespace assertType
- {
- enum Enum
- {
- // macro traits
-
- is_warn = 1,
- is_check = 2,
- is_require = 4,
-
- is_throws = 8,
- is_throws_as = 16,
- is_nothrow = 32,
-
- is_fast = 64, // not checked anywhere - used just to distinguish the types
- is_false = 128,
- is_unary = 256,
-
- is_eq = 512,
- is_ne = 1024,
-
- is_lt = 2048,
- is_gt = 4096,
-
- is_ge = 8192,
- is_le = 16384,
-
- // macro types
-
- DT_WARN = is_warn,
- DT_CHECK = is_check,
- DT_REQUIRE = is_require,
-
- DT_WARN_FALSE = is_false | is_warn,
- DT_CHECK_FALSE = is_false | is_check,
- DT_REQUIRE_FALSE = is_false | is_require,
-
- DT_WARN_THROWS = is_throws | is_warn,
- DT_CHECK_THROWS = is_throws | is_check,
- DT_REQUIRE_THROWS = is_throws | is_require,
-
- DT_WARN_THROWS_AS = is_throws_as | is_warn,
- DT_CHECK_THROWS_AS = is_throws_as | is_check,
- DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
-
- DT_WARN_NOTHROW = is_nothrow | is_warn,
- DT_CHECK_NOTHROW = is_nothrow | is_check,
- DT_REQUIRE_NOTHROW = is_nothrow | is_require,
-
- DT_WARN_EQ = is_eq | is_warn,
- DT_CHECK_EQ = is_eq | is_check,
- DT_REQUIRE_EQ = is_eq | is_require,
-
- DT_WARN_NE = is_ne | is_warn,
- DT_CHECK_NE = is_ne | is_check,
- DT_REQUIRE_NE = is_ne | is_require,
-
- DT_WARN_GT = is_gt | is_warn,
- DT_CHECK_GT = is_gt | is_check,
- DT_REQUIRE_GT = is_gt | is_require,
-
- DT_WARN_LT = is_lt | is_warn,
- DT_CHECK_LT = is_lt | is_check,
- DT_REQUIRE_LT = is_lt | is_require,
-
- DT_WARN_GE = is_ge | is_warn,
- DT_CHECK_GE = is_ge | is_check,
- DT_REQUIRE_GE = is_ge | is_require,
-
- DT_WARN_LE = is_le | is_warn,
- DT_CHECK_LE = is_le | is_check,
- DT_REQUIRE_LE = is_le | is_require,
-
- DT_WARN_UNARY = is_unary | is_warn,
- DT_CHECK_UNARY = is_unary | is_check,
- DT_REQUIRE_UNARY = is_unary | is_require,
-
- DT_WARN_UNARY_FALSE = is_false | is_unary | is_warn,
- DT_CHECK_UNARY_FALSE = is_false | is_unary | is_check,
- DT_REQUIRE_UNARY_FALSE = is_false | is_unary | is_require,
-
- DT_FAST_WARN_EQ = is_fast | is_eq | is_warn,
- DT_FAST_CHECK_EQ = is_fast | is_eq | is_check,
- DT_FAST_REQUIRE_EQ = is_fast | is_eq | is_require,
-
- DT_FAST_WARN_NE = is_fast | is_ne | is_warn,
- DT_FAST_CHECK_NE = is_fast | is_ne | is_check,
- DT_FAST_REQUIRE_NE = is_fast | is_ne | is_require,
-
- DT_FAST_WARN_GT = is_fast | is_gt | is_warn,
- DT_FAST_CHECK_GT = is_fast | is_gt | is_check,
- DT_FAST_REQUIRE_GT = is_fast | is_gt | is_require,
-
- DT_FAST_WARN_LT = is_fast | is_lt | is_warn,
- DT_FAST_CHECK_LT = is_fast | is_lt | is_check,
- DT_FAST_REQUIRE_LT = is_fast | is_lt | is_require,
-
- DT_FAST_WARN_GE = is_fast | is_ge | is_warn,
- DT_FAST_CHECK_GE = is_fast | is_ge | is_check,
- DT_FAST_REQUIRE_GE = is_fast | is_ge | is_require,
-
- DT_FAST_WARN_LE = is_fast | is_le | is_warn,
- DT_FAST_CHECK_LE = is_fast | is_le | is_check,
- DT_FAST_REQUIRE_LE = is_fast | is_le | is_require,
-
- DT_FAST_WARN_UNARY = is_fast | is_unary | is_warn,
- DT_FAST_CHECK_UNARY = is_fast | is_unary | is_check,
- DT_FAST_REQUIRE_UNARY = is_fast | is_unary | is_require,
-
- DT_FAST_WARN_UNARY_FALSE = is_fast | is_false | is_unary | is_warn,
- DT_FAST_CHECK_UNARY_FALSE = is_fast | is_false | is_unary | is_check,
- DT_FAST_REQUIRE_UNARY_FALSE = is_fast | is_false | is_unary | is_require
- };
- } // namespace assertType
-
- DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
-
// clang-format off
template<class T> struct decay_array { typedef T type; };
template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; };
@@ -1268,29 +1370,7 @@
DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at);
DOCTEST_INTERFACE void fastAssertThrowIfFlagSet(int flags);
- struct TestAccessibleContextState
- {
- bool no_throw; // to skip exceptions-related assertion macros
- bool success; // include successful assertions in output
- };
-
- struct ContextState;
-
- DOCTEST_INTERFACE TestAccessibleContextState* getTestsContextState();
-
- struct DOCTEST_INTERFACE SubcaseSignature
- {
- const char* m_name;
- const char* m_file;
- int m_line;
-
- SubcaseSignature(const char* name, const char* file, int line)
- : m_name(name)
- , m_file(file)
- , m_line(line) {}
-
- bool operator<(const SubcaseSignature& other) const;
- };
+ DOCTEST_INTERFACE const ContextOptions* getContextOptions();
// cppcheck-suppress copyCtorAndEqOperator
struct DOCTEST_INTERFACE Subcase
@@ -1425,7 +1505,7 @@
bool res = op_macro(lhs, rhs); \
if(m_at & assertType::is_false) \
res = !res; \
- if(!res || doctest::detail::getTestsContextState()->success) \
+ if(!res || doctest::detail::getContextOptions()->success) \
return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \
return Result(res); \
}
@@ -1454,7 +1534,7 @@
if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
res = !res;
- if(!res || getTestsContextState()->success)
+ if(!res || getContextOptions()->success)
return Result(res, toString(lhs));
return Result(res);
}
@@ -1516,14 +1596,9 @@
}
};
- struct DOCTEST_INTERFACE TestCase
+ struct TestSuite
{
- // not used for determining uniqueness
- funcType m_test; // a function pointer to the test case
- String m_full_name; // contains the name (only for templated test cases!) + the template type
- const char* m_name; // name of the test case
- const char* m_type; // for templated test cases - gets appended to the real name
- const char* m_test_suite; // the test suite in which the test was added
+ const char* m_test_suite;
const char* m_description;
bool m_skip;
bool m_may_fail;
@@ -1531,10 +1606,32 @@
int m_expected_failures;
double m_timeout;
- // fields by which uniqueness of test cases shall be determined
- const char* m_file; // the file in which the test was registered
- unsigned m_line; // the line where the test was registered
+ TestSuite& operator*(const char* in) {
+ m_test_suite = in;
+ // clear state
+ m_description = 0;
+ m_skip = false;
+ m_may_fail = false;
+ m_should_fail = false;
+ m_expected_failures = 0;
+ m_timeout = 0;
+ return *this;
+ }
+
+ template <typename T>
+ TestSuite& operator*(const T& in) {
+ in.fill(*this);
+ return *this;
+ }
+ };
+
+ struct DOCTEST_INTERFACE TestCase : public TestCaseData
+ {
+ detail::funcType m_test; // a function pointer to the test case
+
+ const char* m_type; // for templated test cases - gets appended to the real name
int m_template_id; // an ID used to distinguish between the different versions of a templated test case
+ String m_full_name; // contains the name (only for templated test cases!) + the template type
TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
const char* type = "", int template_id = -1);
@@ -1552,7 +1649,9 @@
TestCase(const TestCase& other) { *this = other; }
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
TestCase& operator=(const TestCase& other);
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
bool operator<(const TestCase& other) const;
};
@@ -1584,51 +1683,35 @@
template <class L, class R> struct RelationalComparator<5, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return le(lhs, rhs); } };
// clang-format on
- struct DOCTEST_INTERFACE ResultBuilder
+ struct DOCTEST_INTERFACE ResultBuilder : public AssertData
{
- // common - for all asserts
- const TestCase& m_test_case;
- assertType::Enum m_at;
- const char* m_file;
- int m_line;
- const char* m_expr;
- bool m_failed;
-
- // exception-related - for all asserts
- bool m_threw;
- String m_exception;
-
- // for normal asserts
- Result m_result;
-
- // for specific exception-related asserts
- bool m_threw_as;
- const char* m_exception_type;
-
ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
const char* exception_type = "");
~ResultBuilder();
- void setResult(const Result& res) { m_result = res; }
+ void setResult(const Result& res) {
+ m_decomposition = res.m_decomposition;
+ m_failed = !res.m_passed;
+ }
template <int comparison, typename L, typename R>
DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs,
const DOCTEST_REF_WRAP(R) rhs) {
- m_result.m_passed = RelationalComparator<comparison, L, R>()(lhs, rhs);
- if(!m_result.m_passed || getTestsContextState()->success)
- m_result.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
+ m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+ if(m_failed || getContextOptions()->success)
+ m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
}
template <typename L>
DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) {
- m_result.m_passed = !!val;
+ m_failed = !val;
if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
- m_result.m_passed = !m_result.m_passed;
+ m_failed = !m_failed;
- if(!m_result.m_passed || getTestsContextState()->success)
- m_result.m_decomposition = toString(val);
+ if(m_failed || getContextOptions()->success)
+ m_decomposition = toString(val);
}
void unexpectedExceptionOccurred();
@@ -1653,10 +1736,10 @@
const DOCTEST_REF_WRAP(R) rhs) {
ResultBuilder rb(at, file, line, expr);
- rb.m_result.m_passed = RelationalComparator<comparison, L, R>()(lhs, rhs);
+ rb.m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
- if(!rb.m_result.m_passed || getTestsContextState()->success)
- rb.m_result.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
+ if(rb.m_failed || getContextOptions()->success)
+ rb.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
int res = 0;
@@ -1684,13 +1767,13 @@
const char* val_str, const DOCTEST_REF_WRAP(L) val) {
ResultBuilder rb(at, file, line, val_str);
- rb.m_result.m_passed = !!val;
+ rb.m_failed = !val;
if(at & assertType::is_false) //!OCLINT bitwise operator in conditional
- rb.m_result.m_passed = !rb.m_result.m_passed;
+ rb.m_failed = !rb.m_failed;
- if(!rb.m_result.m_passed || getTestsContextState()->success)
- rb.m_result.m_decomposition = toString(val);
+ if(rb.m_failed || getContextOptions()->success)
+ rb.m_decomposition = toString(val);
int res = 0;
@@ -1806,15 +1889,9 @@
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
#endif // DOCTEST_CONFIG_WITH_LONG_LONG
- struct IContextScope
- {
- virtual ~IContextScope() {}
- virtual void build(std::ostream*) = 0;
- };
-
DOCTEST_INTERFACE void addToContexts(IContextScope* ptr);
DOCTEST_INTERFACE void popFromContexts();
- DOCTEST_INTERFACE void useContextIfExceptionOccurred(IContextScope* ptr);
+ DOCTEST_INTERFACE void stringifyContextIfExceptionOccurred(IContextScope* ptr);
// cppcheck-suppress copyCtorAndEqOperator
class ContextBuilder
@@ -1834,9 +1911,7 @@
explicit Capture(const T* in)
: capture(in) {}
- virtual void toStream(std::ostream* s) const { // override
- detail::toStream(s, *capture);
- }
+ void toStream(std::ostream* s) const DOCTEST_OVERRIDE { detail::toStream(s, *capture); }
};
struct Chunk
@@ -1857,7 +1932,7 @@
Node* tail;
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align")
- void build(std::ostream* s) const {
+ void stringify(std::ostream* s) const {
int curr = 0;
// iterate over small buffer
while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)
@@ -1950,21 +2025,17 @@
}
DOCTEST_NOINLINE ~ContextScope() {
- useContextIfExceptionOccurred(this);
+ stringifyContextIfExceptionOccurred(this);
popFromContexts();
}
- void build(std::ostream* s) { contextBuilder.build(s); }
+ void stringify(std::ostream* s) const { contextBuilder.stringify(s); }
};
- class DOCTEST_INTERFACE MessageBuilder
+ struct DOCTEST_INTERFACE MessageBuilder : public MessageData
{
- std::ostream* m_stream;
- const char* m_file;
- int m_line;
- assertType::Enum m_severity;
+ std::ostream* m_stream;
- public:
MessageBuilder(const char* file, int line, assertType::Enum severity);
~MessageBuilder();
@@ -1974,7 +2045,6 @@
return *this;
}
- void log(std::ostream&);
bool log();
void react();
};
@@ -1998,9 +2068,6 @@
DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);
DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0);
-#endif // DOCTEST_CONFIG_DISABLE
-
-#ifndef DOCTEST_CONFIG_DISABLE
template <typename T>
int registerExceptionTranslator(String (*translateFunction)(T)) {
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")
@@ -2010,6 +2077,17 @@
return 0;
}
+} // namespace doctest
+
+// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
+// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
+namespace doctest_detail_test_suite_ns
+{
+DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest
+{
#else // DOCTEST_CONFIG_DISABLE
template <typename T>
int registerExceptionTranslator(String (*)(T)) {
@@ -2019,16 +2097,18 @@
DOCTEST_INTERFACE bool isRunningInTest();
+namespace detail
+{
+ struct ContextState;
+} // namespace detail
+
// cppcheck-suppress noCopyConstructor
class DOCTEST_INTERFACE Context
{
-#if !defined(DOCTEST_CONFIG_DISABLE)
detail::ContextState* p;
void parseArgs(int argc, const char* const* argv, bool withDefaults = false);
-#endif // DOCTEST_CONFIG_DISABLE
-
public:
explicit Context(int argc = 0, const char* const* argv = 0);
@@ -2046,6 +2126,85 @@
int run();
};
+namespace TestCaseFailureReason
+{
+ enum Enum
+ {
+ None = 0,
+ AssertFailure = 1, // an assertion has failed in the test case
+ Exception = 2, // test case threw an exception
+ Crash = 4, // a crash...
+ TooManyFailedAsserts = 8, // the abort-after option
+ Timeout = 16, // see the timeout decorator
+ ShouldHaveFailedButDidnt = 32, // see the should_fail decorator
+ ShouldHaveFailedAndDid = 64, // see the should_fail decorator
+ DidntFailExactlyNumTimes = 128, // see the expected_failures decorator
+ FailedExactlyNumTimes = 256, // see the expected_failures decorator
+ CouldHaveFailedAndDid = 512 // see the may_fail decorator
+ };
+} // namespace TestCaseFailureReason
+
+struct CurrentTestCaseStats
+{
+ int numAssertsForCurrentTestCase;
+ int numAssertsFailedForCurrentTestCase;
+ double seconds_so_far;
+ int failure_flags; // use TestCaseFailureReason::Enum
+ String error_string;
+ bool should_reenter; // means we are not done with the test case because of subcases
+};
+
+struct TestRunStats
+{
+ unsigned numTestCases;
+ unsigned numTestCasesPassingFilters;
+ unsigned numTestSuitesPassingFilters;
+ unsigned numTestCasesFailed;
+ int numAsserts;
+ int numAssertsFailed;
+};
+
+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;
+ // 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)
+ virtual void test_case_end(const CurrentTestCaseStats&) = 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;
+
+ // called for each assert (don't cache pointers to the input)
+ virtual void log_assert(const AssertData&) = 0;
+ // called for each message (don't cache pointers to the input)
+ virtual void log_message(const MessageData&) = 0;
+
+ // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
+ // or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
+ virtual void test_case_skipped(const TestCaseData&) = 0;
+
+ // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have
+ virtual ~IReporter() {}
+
+ // can obtain all currently active contexts and stringify them if one wishes to do so
+ static int get_num_active_contexts();
+ static const IContextScope* const* get_active_contexts();
+
+ // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown
+ static int get_num_stringified_contexts();
+ static const String* get_stringified_contexts();
+};
+
+int registerReporter(const char* name, int priority, IReporter* r);
+
} // namespace doctest
// if registering is not disabled
@@ -2279,6 +2438,12 @@
DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \
signature)
+// for registering
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
+ doctest::registerReporter(name, priority, reporter); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
// for logging
#define DOCTEST_INFO(x) \
doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \
@@ -2287,7 +2452,7 @@
#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \
do { \
- doctest::detail::MessageBuilder mb(file, line, doctest::detail::assertType::type); \
+ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \
mb << x; \
DOCTEST_ASSERT_LOG_AND_REACT(mb); \
} while((void)0, 0)
@@ -2317,10 +2482,10 @@
#define DOCTEST_ASSERT_IMPLEMENT_2(expr, assert_type) \
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \
DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \
- doctest::detail::ExpressionDecomposer(doctest::detail::assertType::assert_type) \
+ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
<< DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \
DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \
DOCTEST_CLANG_SUPPRESS_WARNING_POP
@@ -2366,9 +2531,9 @@
#define DOCTEST_ASSERT_THROWS(expr, assert_type) \
do { \
- if(!doctest::detail::getTestsContextState()->no_throw) { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \
- __FILE__, __LINE__, #expr); \
+ if(!doctest::detail::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #expr); \
try { \
expr; \
} catch(...) { _DOCTEST_RB.m_threw = true; } \
@@ -2378,9 +2543,9 @@
#define DOCTEST_ASSERT_THROWS_AS(expr, as, assert_type) \
do { \
- if(!doctest::detail::getTestsContextState()->no_throw) { \
+ if(!doctest::detail::getContextOptions()->no_throw) { \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, #expr, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #expr, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(as))); \
try { \
expr; \
@@ -2394,9 +2559,9 @@
#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \
do { \
- if(!doctest::detail::getTestsContextState()->no_throw) { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \
- __FILE__, __LINE__, #expr); \
+ if(!doctest::detail::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #expr); \
try { \
expr; \
} catch(...) { _DOCTEST_RB.unexpectedExceptionOccurred(); } \
@@ -2440,7 +2605,7 @@
#define DOCTEST_BINARY_ASSERT(assert_type, expr, comp) \
do { \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \
DOCTEST_WRAP_IN_TRY( \
_DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \
@@ -2450,8 +2615,8 @@
#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_BINARY_ASSERT(assert_type, lhs, rhs, comp) \
do { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \
- __FILE__, __LINE__, #lhs ", " #rhs); \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #lhs ", " #rhs); \
DOCTEST_WRAP_IN_TRY( \
_DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>(lhs, \
rhs)) \
@@ -2462,7 +2627,7 @@
#define DOCTEST_UNARY_ASSERT(assert_type, expr) \
do { \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \
DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \
DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
@@ -2529,7 +2694,7 @@
do { \
int _DOCTEST_FAST_RES = doctest::detail::fast_binary_assert< \
doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr)); \
if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \
@@ -2541,8 +2706,7 @@
do { \
int _DOCTEST_FAST_RES = doctest::detail::fast_binary_assert< \
doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, \
- rhs); \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, rhs); \
if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \
DOCTEST_BREAK_INTO_DEBUGGER(); \
doctest::detail::fastAssertThrowIfFlagSet(_DOCTEST_FAST_RES); \
@@ -2552,7 +2716,7 @@
#define DOCTEST_FAST_UNARY_ASSERT(assert_type, expr) \
do { \
int _DOCTEST_FAST_RES = doctest::detail::fast_unary_assert( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr)); \
if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \
@@ -2565,19 +2729,17 @@
#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_FAST_BINARY_ASSERT(assert_type, expr, comparison) \
doctest::detail::fast_binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr))
#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_FAST_BINARY_ASSERT(assert_type, lhs, rhs, comparison) \
doctest::detail::fast_binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, \
- rhs)
+ doctest::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, rhs)
#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_FAST_UNARY_ASSERT(assert_type, expr) \
- doctest::detail::fast_unary_assert(doctest::detail::assertType::assert_type, __FILE__, \
- __LINE__, \
+ doctest::detail::fast_unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr))
@@ -2782,6 +2944,8 @@
template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature)
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+
#define DOCTEST_INFO(x) ((void)0)
#define DOCTEST_CAPTURE(x) ((void)0)
#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0)
@@ -2983,6 +3147,7 @@
#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN
#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR
+#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER
#define INFO DOCTEST_INFO
#define CAPTURE DOCTEST_CAPTURE
#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT
@@ -3086,6 +3251,8 @@
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
// this is here to clear the 'current test suite' for the current translation unit - at the top
DOCTEST_TEST_SUITE_END();
@@ -3115,6 +3282,8 @@
} // namespace detail
} // namespace doctest
+#endif // DOCTEST_CONFIG_DISABLE
+
DOCTEST_CLANG_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
DOCTEST_GCC_SUPPRESS_WARNING_POP
@@ -3136,6 +3305,7 @@
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")
@@ -3192,40 +3362,28 @@
DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C
DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff
+DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)
+// static analysis
+DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
+DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
+DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
+DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr...
#if defined(DOCTEST_NO_CPP11_COMPAT)
DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
#endif // DOCTEST_NO_CPP11_COMPAT
-#define DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(f, arg) \
- do { \
- f(std::cout arg); \
- if(isDebuggerActive()) { \
- ContextState* p_cs = contextState; \
- bool with_col = p_cs->no_colors; \
- p_cs->no_colors = false; \
- std::ostringstream oss; \
- f(oss arg); \
- printToDebugConsole(oss.str().c_str()); \
- p_cs->no_colors = with_col; \
- } \
- } while(false)
-
-#define DOCTEST_LOG_START \
- do { \
- if(!contextState->hasLoggedCurrentTestStart) { \
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(logTestStart, \
- DOCTEST_COMMA * contextState->currentTest); \
- contextState->hasLoggedCurrentTestStart = true; \
- } \
- } while(false)
-
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
// required includes - will go only in one translation unit!
#include <ctime>
#include <cmath>
+#include <climits>
// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37
#ifdef __BORLANDC__
#include <math.h>
@@ -3242,6 +3400,7 @@
#include <iomanip>
#include <vector>
#include <set>
+#include <map>
#include <exception>
#include <stdexcept>
#include <csignal>
@@ -3326,83 +3485,39 @@
#ifndef DOCTEST_CONFIG_DISABLE
- // this holds both parameters for the command line and runtime data for tests
- struct ContextState : TestAccessibleContextState //!OCLINT too many fields
+ // this holds both parameters from the command line and runtime data for tests
+ struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
{
- // == parameters from the command line
-
std::vector<std::vector<String> > filters;
- String order_by; // how tests should be ordered
- unsigned rand_seed; // the seed for rand ordering
+ std::vector<IReporter*> reporters_currently_used;
- unsigned first; // the first (matching) test to be executed
- unsigned last; // the last (matching) test to be executed
-
- int abort_after; // stop tests after this many failed assertions
- int subcase_filter_levels; // apply the subcase filters for the first N levels
- bool case_sensitive; // if filtering should be case sensitive
- bool exit; // if the program should be exited after the tests are ran/whatever
- bool duration; // print the time duration of each test case
- bool no_exitcode; // if the framework should return 0 as the exitcode
- bool no_run; // to not run the tests at all (can be done with an "*" exclude)
- bool no_version; // to not print the version of the framework
- bool no_colors; // if output to the console should be colorized
- bool force_colors; // forces the use of colors even when a tty cannot be detected
- bool no_breaks; // to not break into the debugger
- bool no_skip; // don't skip test cases which are marked to be skipped
- bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x):
- bool no_path_in_filenames; // if the path to files should be removed from the output
- bool no_line_numbers; // if source code line numbers should be omitted from the output
- bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
-
- bool help; // to print the help
- bool version; // to print the version
- bool count; // if only the count of matching tests is to be retreived
- bool list_test_cases; // to list all tests matching the filters
- bool list_test_suites; // to list all suites matching the filters
-
- // == data for the tests being ran
-
- unsigned numTestsPassingFilters;
- unsigned numTestSuitesPassingFilters;
- unsigned numFailed;
const TestCase* currentTest;
- bool hasLoggedCurrentTestStart;
- int numAssertionsForCurrentTestcase;
- int numAssertions;
- int numFailedAssertionsForCurrentTestcase;
- int numFailedAssertions;
- bool hasCurrentTestFailed;
std::vector<IContextScope*> contexts; // for logging with INFO() and friends
- std::vector<std::string> exceptionalContexts; // logging from INFO() due to an exception
+ std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
// stuff for subcases
std::set<SubcaseSignature> subcasesPassed;
std::set<int> subcasesEnteredLevels;
- std::vector<Subcase> subcasesStack;
int subcasesCurrentLevel;
- bool subcasesHasSkipped;
void resetRunData() {
- numTestsPassingFilters = 0;
- numTestSuitesPassingFilters = 0;
- numFailed = 0;
- numAssertions = 0;
- numFailedAssertions = 0;
- numFailedAssertionsForCurrentTestcase = 0;
+ numTestCases = 0;
+ numTestCasesPassingFilters = 0;
+ numTestSuitesPassingFilters = 0;
+ numTestCasesFailed = 0;
+ numAsserts = 0;
+ numAssertsFailed = 0;
}
// cppcheck-suppress uninitMemberVar
ContextState()
- : filters(8) // 8 different filters total
- {
- resetRunData();
- }
+ : filters(9) // 9 different filters total
+ {}
};
- ContextState* contextState = 0;
+ ContextState* g_contextState = 0;
#endif // DOCTEST_CONFIG_DISABLE
} // namespace detail
@@ -3512,6 +3627,109 @@
std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }
+namespace detail
+{
+ void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)
+} // namespace detail
+
+namespace Color
+{
+ std::ostream& operator<<(std::ostream& s, Color::Enum code) {
+ detail::color_to_stream(s, code);
+ return s;
+ }
+} // namespace Color
+
+const char* assertString(assertType::Enum at) {
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(
+ 4062) // enumerator 'x' in switch of enum 'y' is not handled
+ switch(at) { //!OCLINT missing default in switch statements
+ // clang-format off
+ case assertType::DT_WARN : return "WARN";
+ case assertType::DT_CHECK : return "CHECK";
+ case assertType::DT_REQUIRE : return "REQUIRE";
+
+ case assertType::DT_WARN_FALSE : return "WARN_FALSE";
+ case assertType::DT_CHECK_FALSE : return "CHECK_FALSE";
+ case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE";
+
+ case assertType::DT_WARN_THROWS : return "WARN_THROWS";
+ case assertType::DT_CHECK_THROWS : return "CHECK_THROWS";
+ case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS";
+
+ case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS";
+ case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS";
+ case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS";
+
+ case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW";
+ case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW";
+ case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW";
+
+ case assertType::DT_WARN_EQ : return "WARN_EQ";
+ case assertType::DT_CHECK_EQ : return "CHECK_EQ";
+ case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ";
+ case assertType::DT_WARN_NE : return "WARN_NE";
+ case assertType::DT_CHECK_NE : return "CHECK_NE";
+ case assertType::DT_REQUIRE_NE : return "REQUIRE_NE";
+ case assertType::DT_WARN_GT : return "WARN_GT";
+ case assertType::DT_CHECK_GT : return "CHECK_GT";
+ case assertType::DT_REQUIRE_GT : return "REQUIRE_GT";
+ case assertType::DT_WARN_LT : return "WARN_LT";
+ case assertType::DT_CHECK_LT : return "CHECK_LT";
+ case assertType::DT_REQUIRE_LT : return "REQUIRE_LT";
+ case assertType::DT_WARN_GE : return "WARN_GE";
+ case assertType::DT_CHECK_GE : return "CHECK_GE";
+ case assertType::DT_REQUIRE_GE : return "REQUIRE_GE";
+ case assertType::DT_WARN_LE : return "WARN_LE";
+ case assertType::DT_CHECK_LE : return "CHECK_LE";
+ case assertType::DT_REQUIRE_LE : return "REQUIRE_LE";
+
+ case assertType::DT_WARN_UNARY : return "WARN_UNARY";
+ case assertType::DT_CHECK_UNARY : return "CHECK_UNARY";
+ case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY";
+ case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE";
+ case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE";
+ case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE";
+
+ case assertType::DT_FAST_WARN_EQ : return "FAST_WARN_EQ";
+ case assertType::DT_FAST_CHECK_EQ : return "FAST_CHECK_EQ";
+ case assertType::DT_FAST_REQUIRE_EQ : return "FAST_REQUIRE_EQ";
+ case assertType::DT_FAST_WARN_NE : return "FAST_WARN_NE";
+ case assertType::DT_FAST_CHECK_NE : return "FAST_CHECK_NE";
+ case assertType::DT_FAST_REQUIRE_NE : return "FAST_REQUIRE_NE";
+ case assertType::DT_FAST_WARN_GT : return "FAST_WARN_GT";
+ case assertType::DT_FAST_CHECK_GT : return "FAST_CHECK_GT";
+ case assertType::DT_FAST_REQUIRE_GT : return "FAST_REQUIRE_GT";
+ case assertType::DT_FAST_WARN_LT : return "FAST_WARN_LT";
+ case assertType::DT_FAST_CHECK_LT : return "FAST_CHECK_LT";
+ case assertType::DT_FAST_REQUIRE_LT : return "FAST_REQUIRE_LT";
+ case assertType::DT_FAST_WARN_GE : return "FAST_WARN_GE";
+ case assertType::DT_FAST_CHECK_GE : return "FAST_CHECK_GE";
+ case assertType::DT_FAST_REQUIRE_GE : return "FAST_REQUIRE_GE";
+ case assertType::DT_FAST_WARN_LE : return "FAST_WARN_LE";
+ case assertType::DT_FAST_CHECK_LE : return "FAST_CHECK_LE";
+ case assertType::DT_FAST_REQUIRE_LE : return "FAST_REQUIRE_LE";
+
+ case assertType::DT_FAST_WARN_UNARY : return "FAST_WARN_UNARY";
+ case assertType::DT_FAST_CHECK_UNARY : return "FAST_CHECK_UNARY";
+ case assertType::DT_FAST_REQUIRE_UNARY : return "FAST_REQUIRE_UNARY";
+ case assertType::DT_FAST_WARN_UNARY_FALSE : return "FAST_WARN_UNARY_FALSE";
+ case assertType::DT_FAST_CHECK_UNARY_FALSE : return "FAST_CHECK_UNARY_FALSE";
+ case assertType::DT_FAST_REQUIRE_UNARY_FALSE: return "FAST_REQUIRE_UNARY_FALSE";
+ // clang-format on
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ return "";
+}
+
+bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
+ if(m_line != other.m_line)
+ return m_line < other.m_line;
+ if(std::strcmp(m_file, other.m_file) != 0)
+ return std::strcmp(m_file, other.m_file) < 0;
+ return std::strcmp(m_name, other.m_name) < 0;
+}
+
Approx::Approx(double value)
: m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)
, m_scale(1.0)
@@ -3620,6 +3838,14 @@
void Context::setOption(const char*, const char*) {}
bool Context::shouldExit() { return false; }
int Context::run() { return 0; }
+
+int IReporter::get_num_active_contexts() { return 0; }
+const IContextScope* const* IReporter::get_active_contexts() { return 0; }
+int IReporter::get_num_stringified_contexts() { return 0; }
+const String* IReporter::get_stringified_contexts() { return 0; }
+
+int registerReporter(const char*, int, IReporter*) { return 0; }
+
} // namespace doctest
#else // DOCTEST_CONFIG_DISABLE
@@ -3692,21 +3918,33 @@
{
namespace detail
{
+ typedef std::map<std::pair<int, String>, IReporter*> reporterMap;
+ reporterMap& getReporters() {
+ static reporterMap data;
+ return data;
+ }
+
+#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, args) \
+ for(size_t iii = 0; iii < g_contextState->reporters_currently_used.size(); ++iii) \
+ g_contextState->reporters_currently_used[iii]->function(args)
+
TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
- const char* type, int template_id)
- : m_test(test)
- , m_name(0)
- , m_type(type)
- , m_test_suite(test_suite.m_test_suite)
- , m_description(test_suite.m_description)
- , m_skip(test_suite.m_skip)
- , m_may_fail(test_suite.m_may_fail)
- , m_should_fail(test_suite.m_should_fail)
- , m_expected_failures(test_suite.m_expected_failures)
- , m_timeout(test_suite.m_timeout)
- , m_file(file)
- , m_line(line)
- , m_template_id(template_id) {}
+ const char* type, int template_id) {
+ m_file = file;
+ m_line = line;
+ m_name = 0;
+ m_test_suite = test_suite.m_test_suite;
+ m_description = test_suite.m_description;
+ m_skip = test_suite.m_skip;
+ m_may_fail = test_suite.m_may_fail;
+ m_should_fail = test_suite.m_should_fail;
+ m_expected_failures = test_suite.m_expected_failures;
+ m_timeout = test_suite.m_timeout;
+
+ m_test = test;
+ m_type = type;
+ m_template_id = template_id;
+ }
TestCase& TestCase::operator*(const char* in) {
m_name = in;
@@ -3720,10 +3958,9 @@
}
TestCase& TestCase::operator=(const TestCase& other) {
- m_test = other.m_test;
- m_full_name = other.m_full_name;
+ m_file = other.m_file;
+ m_line = other.m_line;
m_name = other.m_name;
- m_type = other.m_type;
m_test_suite = other.m_test_suite;
m_description = other.m_description;
m_skip = other.m_skip;
@@ -3731,9 +3968,11 @@
m_should_fail = other.m_should_fail;
m_expected_failures = other.m_expected_failures;
m_timeout = other.m_timeout;
- m_file = other.m_file;
- m_line = other.m_line;
- m_template_id = other.m_template_id;
+
+ m_test = other.m_test;
+ m_type = other.m_type;
+ m_template_id = other.m_template_id;
+ m_full_name = other.m_full_name;
if(m_template_id != -1)
m_name = m_full_name.c_str();
@@ -3749,95 +3988,13 @@
return m_template_id < other.m_template_id;
}
- const char* assertString(assertType::Enum at) {
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(
- 4062) // enumerator 'x' in switch of enum 'y' is not handled
- switch(at) { //!OCLINT missing default in switch statements
- // clang-format off
- case assertType::DT_WARN : return "WARN";
- case assertType::DT_CHECK : return "CHECK";
- case assertType::DT_REQUIRE : return "REQUIRE";
-
- case assertType::DT_WARN_FALSE : return "WARN_FALSE";
- case assertType::DT_CHECK_FALSE : return "CHECK_FALSE";
- case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE";
-
- case assertType::DT_WARN_THROWS : return "WARN_THROWS";
- case assertType::DT_CHECK_THROWS : return "CHECK_THROWS";
- case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS";
-
- case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS";
- case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS";
- case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS";
-
- case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW";
- case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW";
- case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW";
-
- case assertType::DT_WARN_EQ : return "WARN_EQ";
- case assertType::DT_CHECK_EQ : return "CHECK_EQ";
- case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ";
- case assertType::DT_WARN_NE : return "WARN_NE";
- case assertType::DT_CHECK_NE : return "CHECK_NE";
- case assertType::DT_REQUIRE_NE : return "REQUIRE_NE";
- case assertType::DT_WARN_GT : return "WARN_GT";
- case assertType::DT_CHECK_GT : return "CHECK_GT";
- case assertType::DT_REQUIRE_GT : return "REQUIRE_GT";
- case assertType::DT_WARN_LT : return "WARN_LT";
- case assertType::DT_CHECK_LT : return "CHECK_LT";
- case assertType::DT_REQUIRE_LT : return "REQUIRE_LT";
- case assertType::DT_WARN_GE : return "WARN_GE";
- case assertType::DT_CHECK_GE : return "CHECK_GE";
- case assertType::DT_REQUIRE_GE : return "REQUIRE_GE";
- case assertType::DT_WARN_LE : return "WARN_LE";
- case assertType::DT_CHECK_LE : return "CHECK_LE";
- case assertType::DT_REQUIRE_LE : return "REQUIRE_LE";
-
- case assertType::DT_WARN_UNARY : return "WARN_UNARY";
- case assertType::DT_CHECK_UNARY : return "CHECK_UNARY";
- case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY";
- case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE";
- case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE";
- case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE";
-
- case assertType::DT_FAST_WARN_EQ : return "FAST_WARN_EQ";
- case assertType::DT_FAST_CHECK_EQ : return "FAST_CHECK_EQ";
- case assertType::DT_FAST_REQUIRE_EQ : return "FAST_REQUIRE_EQ";
- case assertType::DT_FAST_WARN_NE : return "FAST_WARN_NE";
- case assertType::DT_FAST_CHECK_NE : return "FAST_CHECK_NE";
- case assertType::DT_FAST_REQUIRE_NE : return "FAST_REQUIRE_NE";
- case assertType::DT_FAST_WARN_GT : return "FAST_WARN_GT";
- case assertType::DT_FAST_CHECK_GT : return "FAST_CHECK_GT";
- case assertType::DT_FAST_REQUIRE_GT : return "FAST_REQUIRE_GT";
- case assertType::DT_FAST_WARN_LT : return "FAST_WARN_LT";
- case assertType::DT_FAST_CHECK_LT : return "FAST_CHECK_LT";
- case assertType::DT_FAST_REQUIRE_LT : return "FAST_REQUIRE_LT";
- case assertType::DT_FAST_WARN_GE : return "FAST_WARN_GE";
- case assertType::DT_FAST_CHECK_GE : return "FAST_CHECK_GE";
- case assertType::DT_FAST_REQUIRE_GE : return "FAST_REQUIRE_GE";
- case assertType::DT_FAST_WARN_LE : return "FAST_WARN_LE";
- case assertType::DT_FAST_CHECK_LE : return "FAST_CHECK_LE";
- case assertType::DT_FAST_REQUIRE_LE : return "FAST_REQUIRE_LE";
-
- case assertType::DT_FAST_WARN_UNARY : return "FAST_WARN_UNARY";
- case assertType::DT_FAST_CHECK_UNARY : return "FAST_CHECK_UNARY";
- case assertType::DT_FAST_REQUIRE_UNARY : return "FAST_REQUIRE_UNARY";
- case assertType::DT_FAST_WARN_UNARY_FALSE : return "FAST_WARN_UNARY_FALSE";
- case assertType::DT_FAST_CHECK_UNARY_FALSE : return "FAST_CHECK_UNARY_FALSE";
- case assertType::DT_FAST_REQUIRE_UNARY_FALSE: return "FAST_REQUIRE_UNARY_FALSE";
- // clang-format on
- }
- DOCTEST_MSVC_SUPPRESS_WARNING_POP
- return "";
- }
-
bool checkIfShouldThrow(assertType::Enum at) {
if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
return true;
if((at & assertType::is_check) //!OCLINT bitwise operator in conditional
- && contextState->abort_after > 0 &&
- contextState->numFailedAssertions >= contextState->abort_after)
+ && g_contextState->abort_after > 0 &&
+ g_contextState->numAssertsFailed >= g_contextState->abort_after)
return true;
return false;
@@ -3900,7 +4057,7 @@
//}
// checks if the name matches any of the filters (and can be configured what to do when empty)
- bool matchesAny(const char* name, const std::vector<String>& filters, int matchEmpty,
+ bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,
bool caseSensitive) {
if(filters.empty() && matchEmpty)
return true;
@@ -3955,23 +4112,14 @@
UInt64 m_ticks;
};
- TestAccessibleContextState* getTestsContextState() { return contextState; }
+ Timer g_timer;
- // TODO: remove this from here
- void logTestEnd();
-
- bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
- if(m_line != other.m_line)
- return m_line < other.m_line;
- if(std::strcmp(m_file, other.m_file) != 0)
- return std::strcmp(m_file, other.m_file) < 0;
- return std::strcmp(m_name, other.m_name) < 0;
- }
+ const ContextOptions* getContextOptions() { return g_contextState; }
Subcase::Subcase(const char* name, const char* file, int line)
: m_signature(name, file, line)
, m_entered(false) {
- ContextState* s = contextState;
+ ContextState* s = g_contextState;
// if we have already completed it
if(s->subcasesPassed.count(m_signature) != 0)
@@ -3979,25 +4127,22 @@
// check subcase filters
if(s->subcasesCurrentLevel < s->subcase_filter_levels) {
- if(!matchesAny(m_signature.m_name, s->filters[6], 1, s->case_sensitive))
+ if(!matchesAny(m_signature.m_name, s->filters[6], true, s->case_sensitive))
return;
- if(matchesAny(m_signature.m_name, s->filters[7], 0, s->case_sensitive))
+ if(matchesAny(m_signature.m_name, s->filters[7], false, s->case_sensitive))
return;
}
// if a Subcase on the same level has already been entered
if(s->subcasesEnteredLevels.count(s->subcasesCurrentLevel) != 0) {
- s->subcasesHasSkipped = true;
+ s->should_reenter = true;
return;
}
- s->subcasesStack.push_back(*this);
- if(s->hasLoggedCurrentTestStart)
- logTestEnd();
- s->hasLoggedCurrentTestStart = false;
-
s->subcasesEnteredLevels.insert(s->subcasesCurrentLevel++);
m_entered = true;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
}
Subcase::Subcase(const Subcase& other)
@@ -4007,18 +4152,14 @@
Subcase::~Subcase() {
if(m_entered) {
- ContextState* s = contextState;
+ ContextState* s = g_contextState;
s->subcasesCurrentLevel--;
// only mark the subcase as passed if no subcases have been skipped
- if(s->subcasesHasSkipped == false)
+ if(s->should_reenter == false)
s->subcasesPassed.insert(m_signature);
- if(!s->subcasesStack.empty())
- s->subcasesStack.pop_back();
- if(s->hasLoggedCurrentTestStart)
- logTestEnd();
- s->hasLoggedCurrentTestStart = false;
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, m_signature);
}
}
@@ -4087,65 +4228,41 @@
return 0;
}
- namespace Color
- {
- enum Code
- {
- None = 0,
- White,
- Red,
- Green,
- Blue,
- Cyan,
- Yellow,
- Grey,
-
- Bright = 0x10,
-
- BrightRed = Bright | Red,
- BrightGreen = Bright | Green,
- LightGrey = Bright | Grey,
- BrightWhite = Bright | White
- };
-
#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- HANDLE g_stdoutHandle;
- WORD g_originalForegroundAttributes;
- WORD g_originalBackgroundAttributes;
- bool g_attrsInitted = false;
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ HANDLE g_stdoutHandle;
+ WORD g_origFgAttrs;
+ WORD g_origBgAttrs;
+ bool g_attrsInitted = false;
- void init() {
-#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- if(!g_attrsInitted) {
- g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
- g_attrsInitted = true;
- CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
- GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo);
- g_originalForegroundAttributes =
- csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
- BACKGROUND_BLUE | BACKGROUND_INTENSITY);
- g_originalBackgroundAttributes =
- csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
- FOREGROUND_BLUE | FOREGROUND_INTENSITY);
- }
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ int colors_init() {
+ if(!g_attrsInitted) {
+ g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ g_attrsInitted = true;
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo);
+ g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+ g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY);
}
+ return 0;
+ }
- std::ostream& operator<<(std::ostream& s, Color::Code
-#ifndef DOCTEST_CONFIG_COLORS_NONE
- code
-#endif // DOCTEST_CONFIG_COLORS_NONE
- ) {
- const ContextState* p = contextState;
- if(p->no_colors)
- return s;
+ int dumy_init_console_colors = colors_init();
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+
+ void color_to_stream(std::ostream& s, Color::Enum code) {
+ ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
+ ((void)code); // for DOCTEST_CONFIG_COLORS_NONE
+ const ContextState* p = g_contextState;
+ if(p->no_colors)
+ return;
#ifdef DOCTEST_CONFIG_COLORS_ANSI
- if(isatty(STDOUT_FILENO) == false && p->force_colors == false)
- return s;
+ if(isatty(STDOUT_FILENO) == false && p->force_colors == false)
+ return;
- const char* col = "";
- // clang-format off
+ const char* col = "";
+ // clang-format off
switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement
case Color::Red: col = "[0;31m"; break;
case Color::Green: col = "[0;32m"; break;
@@ -4162,40 +4279,36 @@
case Color::White:
default: col = "[0m";
}
- // clang-format on
- s << "\033" << col;
+ // clang-format on
+ s << "\033" << col;
#endif // DOCTEST_CONFIG_COLORS_ANSI
#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- if(isatty(fileno(stdout)) == false && p->force_colors == false)
- return s;
+ if(isatty(fileno(stdout)) == false && p->force_colors == false)
+ return;
-#define DOCTEST_SET_ATTR(x) \
- SetConsoleTextAttribute(g_stdoutHandle, x | g_originalBackgroundAttributes)
+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs)
- // clang-format off
- switch (code) {
- case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
- case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break;
- case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break;
- case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break;
- case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break;
- case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break;
- case Color::Grey: DOCTEST_SET_ATTR(0); break;
- case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break;
- case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break;
- case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;
- case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
- case Color::None:
- case Color::Bright: // invalid
- default: DOCTEST_SET_ATTR(g_originalForegroundAttributes);
- }
- // clang-format on
-#undef DOCTEST_SET_ATTR
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
- return s;
+ // clang-format off
+ switch (code) {
+ case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break;
+ case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break;
+ case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break;
+ case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break;
+ case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break;
+ case Color::Grey: DOCTEST_SET_ATTR(0); break;
+ case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break;
+ case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break;
+ case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;
+ case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::None:
+ case Color::Bright: // invalid
+ default: DOCTEST_SET_ATTR(g_origFgAttrs);
}
- } // namespace Color
+ // clang-format on
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ }
std::vector<const IExceptionTranslator*>& getExceptionTranslators() {
static std::vector<const IExceptionTranslator*> data;
@@ -4259,22 +4372,20 @@
void toStream(std::ostream* s, int long long unsigned in) { *s << in; }
#endif // DOCTEST_CONFIG_WITH_LONG_LONG
- void addToContexts(IContextScope* ptr) { contextState->contexts.push_back(ptr); }
- void popFromContexts() { contextState->contexts.pop_back(); }
+ void addToContexts(IContextScope* ptr) { g_contextState->contexts.push_back(ptr); }
+ void popFromContexts() { g_contextState->contexts.pop_back(); }
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
- void useContextIfExceptionOccurred(IContextScope* ptr) {
+ void stringifyContextIfExceptionOccurred(IContextScope* ptr) {
if(std::uncaught_exception()) {
std::ostringstream s;
- ptr->build(&s);
- contextState->exceptionalContexts.push_back(s.str());
+ ptr->stringify(&s);
+ g_contextState->stringifiedContexts.push_back(s.str().c_str());
}
}
DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- void printSummary(std::ostream& s);
-
#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
void reportFatal(const std::string&) {}
struct FatalConditionHandler
@@ -4422,53 +4533,6 @@
#endif // DOCTEST_PLATFORM_WINDOWS
#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
- void separator_to_stream(std::ostream& s) {
- s << Color::Yellow
- << "===============================================================================\n";
- }
-
- // depending on the current options this will remove the path of filenames
- const char* fileForOutput(const char* file) {
- if(contextState->no_path_in_filenames) {
- const char* back = std::strrchr(file, '\\');
- const char* forward = std::strrchr(file, '/');
- if(back || forward) {
- if(back > forward)
- forward = back;
- return forward + 1;
- }
- }
- return file;
- }
-
- void file_line_to_stream(std::ostream& s, const char* file, int line, const char* tail = "") {
- s << Color::LightGrey << fileForOutput(file) << (contextState->gnu_file_line ? ":" : "(")
- << (contextState->no_line_numbers ? 0 : line) // 0 or the real num depending on the option
- << (contextState->gnu_file_line ? ":" : "):") << tail;
- }
-
- const char* getSuccessOrFailString(bool success, assertType::Enum at, const char* success_str) {
- if(success)
- return success_str;
- if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
- return "WARNING: ";
- if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
- return "ERROR: ";
- if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
- return "FATAL ERROR: ";
- return "";
- }
-
- Color::Code getSuccessOrFailColor(bool success, assertType::Enum at) {
- return success ? Color::BrightGreen :
- (at & assertType::is_warn) ? Color::Yellow : Color::Red;
- }
-
- void successOrFailColoredStringToStream(std::ostream& s, bool success, assertType::Enum at,
- const char* success_str = "SUCCESS: ") {
- s << getSuccessOrFailColor(success, at) << getSuccessOrFailString(success, at, success_str);
- }
-
#ifdef DOCTEST_PLATFORM_MAC
#include <sys/types.h>
#include <unistd.h>
@@ -4507,134 +4571,54 @@
#endif // Platform
#ifdef DOCTEST_PLATFORM_WINDOWS
- void myOutputDebugString(const String& text) { ::OutputDebugStringA(text.c_str()); }
+ void myOutputDebugString(const char* text) { ::OutputDebugStringA(text); }
#else
// TODO: integration with XCode and other IDEs
- void myOutputDebugString(const String&) {}
+ void myOutputDebugString(const char*) {}
#endif // Platform
- void printToDebugConsole(const String& text) {
- if(isDebuggerActive())
- myOutputDebugString(text);
+ void addAssert(assertType::Enum at) {
+ if((at & assertType::is_warn) == 0) { //!OCLINT bitwise operator in conditional
+ g_contextState->numAsserts++;
+ g_contextState->numAssertsForCurrentTestCase++;
+ }
}
void addFailedAssert(assertType::Enum at) {
if((at & assertType::is_warn) == 0) { //!OCLINT bitwise operator in conditional
- contextState->numFailedAssertions++;
- contextState->numFailedAssertionsForCurrentTestcase++;
- contextState->hasCurrentTestFailed = true;
+ g_contextState->numAssertsFailed++;
+ g_contextState->numAssertsFailedForCurrentTestCase++;
}
}
- std::ostream& operator<<(std::ostream& s, const std::vector<IContextScope*>& contexts) {
- if(!contexts.empty())
- s << Color::None << " logged: ";
- for(size_t i = 0; i < contexts.size(); ++i) {
- s << (i == 0 ? "" : " ");
- contexts[i]->build(&s);
- s << "\n";
- }
- s << "\n";
- return s;
- }
-
- void logTestStart(std::ostream& s, const TestCase& tc) {
- separator_to_stream(s);
- 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')
- s << Color::Yellow << "TEST SUITE: " << Color::None << tc.m_test_suite << "\n";
- if(strncmp(tc.m_name, " Scenario:", 11) != 0)
- s << Color::None << "TEST CASE: ";
- s << Color::None << tc.m_name << "\n";
-
- std::vector<Subcase>& subcasesStack = contextState->subcasesStack;
- for(unsigned i = 0; i < subcasesStack.size(); ++i)
- if(subcasesStack[i].m_signature.m_name[0] != '\0')
- s << " " << subcasesStack[i].m_signature.m_name << "\n";
-
- s << "\n";
- }
-
- void logTestEnd() {}
-
- void logTestException(std::ostream& s, const TestCase& tc, const String& str, bool crash) {
- file_line_to_stream(s, tc.m_file, tc.m_line, " ");
- successOrFailColoredStringToStream(s, false,
- crash ? assertType::is_require : assertType::is_check);
- s << Color::Red << (crash ? "test case CRASHED: " : "test case THREW exception: ")
- << Color::Cyan << str << "\n";
-
- if(!contextState->exceptionalContexts.empty()) {
- s << Color::None << " logged: ";
- for(size_t i = contextState->exceptionalContexts.size(); i > 0; --i)
- s << (i == contextState->exceptionalContexts.size() ? "" : " ")
- << contextState->exceptionalContexts[i - 1] << "\n";
- }
- s << "\n";
- }
-
#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)
void reportFatal(const std::string& message) {
- DOCTEST_LOG_START;
+ g_contextState->seconds_so_far += g_timer.getElapsedSeconds();
+ g_contextState->failure_flags |= TestCaseFailureReason::Crash;
+ g_contextState->error_string = message.c_str();
+ g_contextState->should_reenter = false;
- contextState->numAssertions += contextState->numAssertionsForCurrentTestcase;
+ // TODO: end all currently opened subcases...?
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(
- logTestException,
- DOCTEST_COMMA * contextState->currentTest DOCTEST_COMMA message.c_str()
- DOCTEST_COMMA true);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_contextState);
- logTestEnd();
- contextState->numFailed++;
+ g_contextState->numTestCasesFailed++;
- printSummary(std::cout);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_contextState);
}
#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
- void logAssert(std::ostream& s, const ResultBuilder& rb) {
- file_line_to_stream(s, rb.m_file, rb.m_line, " ");
- successOrFailColoredStringToStream(s, !rb.m_failed, rb.m_at);
- if((rb.m_at & assertType::is_throws_as) == 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_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_result.m_passed ? "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_result.m_decomposition
- << " )\n";
- }
-
- s << contextState->contexts;
- }
-
ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
- const char* exception_type)
- : m_test_case(*contextState->currentTest)
- , m_at(at)
- , m_file(file)
- , m_line(line)
- , m_expr(expr)
- , m_failed(false)
- , m_threw(false)
- , m_threw_as(false)
- , m_exception_type(exception_type) {
+ const char* exception_type) {
+ m_test_case = g_contextState->currentTest;
+ m_at = at;
+ m_file = file;
+ m_line = line;
+ m_expr = expr;
+ m_failed = true;
+ m_threw = false;
+ m_threw_as = false;
+ m_exception_type = exception_type;
#if DOCTEST_MSVC
if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
++m_expr;
@@ -4650,30 +4634,22 @@
}
bool ResultBuilder::log() {
- if((m_at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
- contextState->numAssertionsForCurrentTestcase++;
+ addAssert(m_at);
if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
m_failed = !m_threw;
- } else if(m_at & //!OCLINT bitwise operator in conditional
- assertType::is_throws_as) {
+ } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
m_failed = !m_threw_as;
- } else if(m_at & //!OCLINT bitwise operator in conditional
- assertType::is_nothrow) {
+ } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
m_failed = m_threw;
- } else {
- m_failed = m_result;
}
- if(m_failed || contextState->success) {
- DOCTEST_LOG_START;
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(logAssert, DOCTEST_COMMA * this);
- }
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this);
if(m_failed)
addFailedAssert(m_at);
- return m_failed && isDebuggerActive() && !contextState->no_breaks; // break into debugger
+ return m_failed && isDebuggerActive() && !g_contextState->no_breaks; // break into debugger
}
void ResultBuilder::react() const {
@@ -4681,33 +4657,26 @@
throwException();
}
- MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity)
- : m_stream(createStream())
- , m_file(file)
- , m_line(line)
- , m_severity(severity) {}
-
- void MessageBuilder::log(std::ostream& s) {
- file_line_to_stream(s, m_file, m_line, " ");
- s << getSuccessOrFailColor(false, m_severity)
- << getSuccessOrFailString(m_severity & assertType::is_warn, m_severity, "MESSAGE: ");
- s << Color::None << getStreamResult(m_stream) << "\n";
- s << contextState->contexts;
+ MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {
+ m_stream = createStream();
+ m_file = file;
+ m_line = line;
+ m_severity = severity;
}
bool MessageBuilder::log() {
- DOCTEST_LOG_START;
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(log, DOCTEST_EMPTY);
+ m_string = getStreamResult(m_stream);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
const bool isWarn = m_severity & assertType::is_warn;
// warn is just a message in this context so we dont treat it as an assert
if(!isWarn) {
- contextState->numAssertionsForCurrentTestcase++;
+ addAssert(m_severity);
addFailedAssert(m_severity);
}
- return isDebuggerActive() && !contextState->no_breaks && !isWarn; // break into debugger
+ return isDebuggerActive() && !g_contextState->no_breaks && !isWarn; // break into debugger
}
void MessageBuilder::react() {
@@ -4717,6 +4686,428 @@
MessageBuilder::~MessageBuilder() { freeStream(m_stream); }
+ struct ConsoleReporter : public IReporter
+ {
+ std::ostream& s;
+ bool hasLoggedCurrentTestStart;
+ std::vector<SubcaseSignature> subcasesStack;
+
+ // caching pointers to objects of these types - safe to do
+ const ContextOptions* opt;
+ const TestCaseData* tc;
+
+ ConsoleReporter(std::ostream& in)
+ : s(in) {}
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE
+ // =========================================================================================
+
+ void separator_to_stream() {
+ s << Color::Yellow
+ << "==============================================================================="
+ "\n";
+ }
+
+ // depending on the current options this will remove the path of filenames
+ const char* file_for_output(const char* file) {
+ if(opt->no_path_in_filenames) {
+ const char* back = std::strrchr(file, '\\');
+ const char* forward = std::strrchr(file, '/');
+ if(back || forward) {
+ if(back > forward)
+ forward = back;
+ return forward + 1;
+ }
+ }
+ return file;
+ }
+
+ void file_line_to_stream(const char* file, int line, const char* tail = "") {
+ s << Color::LightGrey << file_for_output(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)
+ return success_str;
+ if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
+ return "WARNING: ";
+ if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
+ return "ERROR: ";
+ if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+ return "FATAL ERROR: ";
+ return "";
+ }
+
+ Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) {
+ return success ? Color::BrightGreen :
+ (at & assertType::is_warn) ? Color::Yellow : Color::Red;
+ }
+
+ void successOrFailColoredStringToStream(bool success, assertType::Enum at,
+ const char* success_str = "SUCCESS: ") {
+ s << getSuccessOrFailColor(success, at)
+ << getSuccessOrFailString(success, at, success_str);
+ }
+
+ void log_contexts() {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ const IContextScope* const* contexts = get_active_contexts();
+
+ s << Color::None << " logged: ";
+ for(int i = 0; i < num_contexts; ++i) {
+ s << (i == 0 ? "" : " ");
+ contexts[i]->stringify(&s);
+ s << "\n";
+ }
+ }
+
+ s << "\n";
+ }
+
+ void logTestStart() {
+ if(hasLoggedCurrentTestStart)
+ return;
+
+ separator_to_stream();
+ file_line_to_stream(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')
+ s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
+ if(strncmp(tc->m_name, " Scenario:", 11) != 0)
+ s << Color::None << "TEST CASE: ";
+ s << Color::None << tc->m_name << "\n";
+
+ for(unsigned i = 0; i < subcasesStack.size(); ++i)
+ if(subcasesStack[i].m_name[0] != '\0')
+ s << " " << subcasesStack[i].m_name << "\n";
+
+ s << "\n";
+
+ hasLoggedCurrentTestStart = true;
+ }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void test_run_start(const ContextOptions& o) DOCTEST_OVERRIDE { opt = &o; }
+
+ void test_run_end(const TestRunStats& p) DOCTEST_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) DOCTEST_OVERRIDE {
+ hasLoggedCurrentTestStart = false;
+ tc = ∈
+ }
+
+ void test_case_end(const CurrentTestCaseStats& st) DOCTEST_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) {
+ const String* 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) DOCTEST_OVERRIDE {
+ subcasesStack.push_back(subc);
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void subcase_end(const SubcaseSignature& /*subc*/) DOCTEST_OVERRIDE {
+ subcasesStack.pop_back();
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void log_assert(const AssertData& rb) DOCTEST_OVERRIDE {
+ if(!rb.m_failed && !opt->success)
+ return;
+
+ 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) == 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_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_decomposition
+ << " )\n";
+ }
+
+ log_contexts();
+ }
+
+ void log_message(const MessageData& mb) DOCTEST_OVERRIDE {
+ 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&) DOCTEST_OVERRIDE {}
+ };
+
+ // 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(g_contextState->no_version == false)
+ s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
+ << DOCTEST_VERSION_STR << "\"\n";
+ }
+
+ void printIntro() {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None << "run with \"--help\" for options\n";
+ }
+
+ void printHelp() {
+ printVersion();
+ // clang-format off
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filter values: \"str1,str2,str3\" (comma separated strings)\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filters use wildcards for matching strings\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "something passes a filter if any of the strings in a filter matches\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"dt-\" PREFIX!!!\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "Query flags - the program quits after them. Available:\n\n";
+ s << " -?, --help, -h prints this message\n";
+ s << " -v, --version prints the version\n";
+ s << " -c, --count prints the number of matching tests\n";
+ s << " -ltc, --list-test-cases lists all matching tests by name\n";
+ s << " -lts, --list-test-suites lists all matching test suites\n";
+ s << " -lr, --list-reporters lists all registered reporters\n\n";
+ // ================================================================================== << 79
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "The available <int>/<string> options/filters are:\n\n";
+ s << " -tc, --test-case=<filters> filters tests by their name\n";
+ s << " -tce, --test-case-exclude=<filters> filters OUT tests by their name\n";
+ s << " -sf, --source-file=<filters> filters tests by their file\n";
+ s << " -sfe, --source-file-exclude=<filters> filters OUT tests by their file\n";
+ s << " -ts, --test-suite=<filters> filters tests by their test suite\n";
+ s << " -tse, --test-suite-exclude=<filters> filters OUT tests by their test suite\n";
+ s << " -sc, --subcase=<filters> filters subcases by their name\n";
+ s << " -sce, --subcase-exclude=<filters> filters OUT subcases by their name\n";
+ s << " -r, --reporters=<filters> reporters to use (console is default)\n";
+ s << " -ob, --order-by=<string> how the tests should be ordered\n";
+ s << " <string> - by [file/suite/name/rand]\n";
+ s << " -rs, --rand-seed=<int> seed for random ordering\n";
+ s << " -f, --first=<int> the first test passing the filters to\n";
+ s << " execute - for range-based execution\n";
+ s << " -l, --last=<int> the last test passing the filters to\n";
+ s << " execute - for range-based execution\n";
+ s << " -aa, --abort-after=<int> stop after <int> failed assertions\n";
+ s << " -scfl,--subcase-filter-levels=<int> apply filters for the first <int> levels\n";
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
+ s << " -s, --success=<bool> include successful assertions in output\n";
+ s << " -cs, --case-sensitive=<bool> filters being treated as case sensitive\n";
+ s << " -e, --exit=<bool> exits after the tests finish\n";
+ s << " -d, --duration=<bool> prints the time duration of each test\n";
+ s << " -nt, --no-throw=<bool> skips exceptions-related assert checks\n";
+ s << " -ne, --no-exitcode=<bool> returns (or exits) always with success\n";
+ s << " -nr, --no-run=<bool> skips all runtime doctest operations\n";
+ s << " -nv, --no-version=<bool> omit the framework version in the output\n";
+ s << " -nc, --no-colors=<bool> disables colors in output\n";
+ s << " -fc, --force-colors=<bool> use colors even when not in a tty\n";
+ s << " -nb, --no-breaks=<bool> disables breakpoints in debuggers\n";
+ s << " -ns, --no-skip=<bool> don't skip test cases marked as skip\n";
+ s << " -gfl, --gnu-file-line=<bool> :n: vs (n): for line numbers in output\n";
+ s << " -npf, --no-path-filenames=<bool> only filenames and no paths in output\n";
+ s << " -nln, --no-line-numbers=<bool> 0 instead of real line numbers in output\n";
+ // ================================================================================== << 79
+ // clang-format on
+
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "for more information visit the project documentation\n\n";
+ }
+
+ void printRegisteredReporters() {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all registered reporters\n";
+ for(reporterMap::iterator it = getReporters().begin(); it != getReporters().end(); ++it)
+ s << "priority: " << std::setw(5) << it->first.first
+ << " name: " << it->first.second << "\n";
+ }
+
+ void output_query_results() {
+ separator_to_stream();
+ if(g_contextState->count || g_contextState->list_test_cases) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_contextState->numTestCasesPassingFilters << "\n";
+ } else if(g_contextState->list_test_suites) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_contextState->numTestCasesPassingFilters << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "test suites with unskipped test cases passing the current filters: "
+ << g_contextState->numTestSuitesPassingFilters << "\n";
+ }
+ }
+
+ void output_query_preamble_test_cases() {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n";
+ separator_to_stream();
+ }
+
+ void output_query_preamble_test_suites() {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+ separator_to_stream();
+ }
+
+ void output_c_string_with_newline(const char* str) { s << Color::None << str << "\n"; }
+ };
+
+ struct DebugOutputWindowReporter : public ConsoleReporter
+ {
+ std::ostringstream oss;
+
+ DebugOutputWindowReporter()
+ : ConsoleReporter(oss) {}
+
+#define DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(func, type) \
+ void func(type in) DOCTEST_OVERRIDE { \
+ if(isDebuggerActive()) { \
+ bool with_col = g_contextState->no_colors; \
+ g_contextState->no_colors = false; \
+ ConsoleReporter::func(in); \
+ myOutputDebugString(oss.str().c_str()); \
+ oss.str(""); \
+ g_contextState->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&)
+ };
+
+ DebugOutputWindowReporter g_debug_output_rep;
+
// the implementation of parseFlag()
bool parseFlagImpl(int argc, const char* const* argv, const char* pattern) {
for(int i = argc - 1; i >= 0; --i) {
@@ -4740,12 +5131,10 @@
// locates a flag on the command line
bool parseFlag(int argc, const char* const* argv, const char* pattern) {
#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- if(!parseFlagImpl(argc, argv, pattern))
- return parseFlagImpl(argc, argv, pattern + 3); // 3 for "dt-"
- return true;
-#else // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- return parseFlagImpl(argc, argv, pattern);
+ if(parseFlagImpl(argc, argv, pattern + 3)) // 3 to skip "dt-"
+ return true;
#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ return parseFlagImpl(argc, argv, pattern);
}
// the implementation of parseOption()
@@ -4780,12 +5169,10 @@
const String& defaultVal = String()) {
res = defaultVal;
#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- if(!parseOptionImpl(argc, argv, pattern, res))
- return parseOptionImpl(argc, argv, pattern + 3, res); // 3 for "dt-"
- return true;
-#else // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- return parseOptionImpl(argc, argv, pattern, res);
+ if(parseOptionImpl(argc, argv, pattern + 3, res)) // 3 to skip "dt-"
+ return true;
#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ return parseOptionImpl(argc, argv, pattern, res);
}
// parses a comma separated list of words after a pattern in one of the arguments in argv
@@ -4847,129 +5234,9 @@
}
return false;
}
-
- void printVersion(std::ostream& s) {
- if(contextState->no_version == false)
- s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
- << DOCTEST_VERSION_STR << "\"\n";
- }
-
- void printHelp(std::ostream& s) {
- printVersion(s);
- // clang-format off
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "filter values: \"str1,str2,str3\" (comma separated strings)\n";
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "filters use wildcards for matching strings\n";
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "something passes a filter if any of the strings in a filter matches\n";
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"dt-\" PREFIX!!!\n";
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "Query flags - the program quits after them. Available:\n\n";
- s << " -?, --help, -h prints this message\n";
- s << " -v, --version prints the version\n";
- s << " -c, --count prints the number of matching tests\n";
- s << " -ltc, --list-test-cases lists all matching tests by name\n";
- s << " -lts, --list-test-suites lists all matching test suites\n\n";
- // ================================================================================== << 79
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "The available <int>/<string> options/filters are:\n\n";
- s << " -tc, --test-case=<filters> filters tests by their name\n";
- s << " -tce, --test-case-exclude=<filters> filters OUT tests by their name\n";
- s << " -sf, --source-file=<filters> filters tests by their file\n";
- s << " -sfe, --source-file-exclude=<filters> filters OUT tests by their file\n";
- s << " -ts, --test-suite=<filters> filters tests by their test suite\n";
- s << " -tse, --test-suite-exclude=<filters> filters OUT tests by their test suite\n";
- s << " -sc, --subcase=<filters> filters subcases by their name\n";
- s << " -sce, --subcase-exclude=<filters> filters OUT subcases by their name\n";
- s << " -ob, --order-by=<string> how the tests should be ordered\n";
- s << " <string> - by [file/suite/name/rand]\n";
- s << " -rs, --rand-seed=<int> seed for random ordering\n";
- s << " -f, --first=<int> the first test passing the filters to\n";
- s << " execute - for range-based execution\n";
- s << " -l, --last=<int> the last test passing the filters to\n";
- s << " execute - for range-based execution\n";
- s << " -aa, --abort-after=<int> stop after <int> failed assertions\n";
- s << " -scfl,--subcase-filter-levels=<int> apply filters for the first <int> levels\n";
- s << Color::Cyan << "\n[doctest] " << Color::None;
- s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
- s << " -s, --success=<bool> include successful assertions in output\n";
- s << " -cs, --case-sensitive=<bool> filters being treated as case sensitive\n";
- s << " -e, --exit=<bool> exits after the tests finish\n";
- s << " -d, --duration=<bool> prints the time duration of each test\n";
- s << " -nt, --no-throw=<bool> skips exceptions-related assert checks\n";
- s << " -ne, --no-exitcode=<bool> returns (or exits) always with success\n";
- s << " -nr, --no-run=<bool> skips all runtime doctest operations\n";
- s << " -nv, --no-version=<bool> omit the framework version in the output\n";
- s << " -nc, --no-colors=<bool> disables colors in output\n";
- s << " -fc, --force-colors=<bool> use colors even when not in a tty\n";
- s << " -nb, --no-breaks=<bool> disables breakpoints in debuggers\n";
- s << " -ns, --no-skip=<bool> don't skip test cases marked as skip\n";
- s << " -gfl, --gnu-file-line=<bool> :n: vs (n): for line numbers in output\n";
- s << " -npf, --no-path-filenames=<bool> only filenames and no paths in output\n";
- s << " -nln, --no-line-numbers=<bool> 0 instead of real line numbers in output\n";
- // ================================================================================== << 79
- // clang-format on
-
- s << Color::Cyan << "\n[doctest] " << Color::None;
- s << "for more information visit the project documentation\n\n";
- }
-
- void printSummary(std::ostream& s) {
- const ContextState* p = contextState;
-
- separator_to_stream(s);
-
- if(p->count || p->list_test_cases) {
- s << Color::Cyan << "[doctest] " << Color::None
- << "unskipped test cases passing the current filters: " << p->numTestsPassingFilters
- << "\n";
- } else if(p->list_test_suites) {
- s << Color::Cyan << "[doctest] " << Color::None
- << "unskipped test cases passing the current filters: " << p->numTestsPassingFilters
- << "\n";
- s << Color::Cyan << "[doctest] " << Color::None
- << "test suites with unskipped test cases passing the current filters: "
- << p->numTestSuitesPassingFilters << "\n";
- } else {
- const bool anythingFailed = p->numFailed > 0 || p->numFailedAssertions > 0;
- s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6)
- << p->numTestsPassingFilters << " | "
- << ((p->numTestsPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green)
- << std::setw(6) << p->numTestsPassingFilters - p->numFailed << " passed"
- << Color::None << " | " << (p->numFailed > 0 ? Color::Red : Color::None)
- << std::setw(6) << p->numFailed << " failed" << Color::None << " | ";
- if(p->no_skipped_summary == false) {
- const int numSkipped = static_cast<unsigned>(getRegisteredTests().size()) -
- p->numTestsPassingFilters;
- 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->numAssertions << " | "
- << ((p->numAssertions == 0 || anythingFailed) ? Color::None : Color::Green)
- << std::setw(6) << (p->numAssertions - p->numFailedAssertions) << " passed"
- << Color::None << " | " << (p->numFailedAssertions > 0 ? Color::Red : Color::None)
- << std::setw(6) << p->numFailedAssertions << " failed" << Color::None << " |\n";
- s << Color::Cyan << "[doctest] " << Color::None
- << "Status: " << (p->numFailed > 0 ? Color::Red : Color::Green)
- << ((p->numFailed > 0) ? "FAILURE!\n" : "SUCCESS!\n");
- }
-
- // remove any coloring
- s << Color::None;
- }
} // namespace detail
-bool isRunningInTest() { return detail::contextState != 0; }
+bool isRunningInTest() { return detail::g_contextState != 0; }
Context::Context(int argc, const char* const* argv)
: p(new detail::ContextState) {
@@ -5001,6 +5268,8 @@
parseCommaSepArgs(argc, argv, "dt-sc=", p->filters[6]);
parseCommaSepArgs(argc, argv, "dt-subcase-exclude=", p->filters[7]);
parseCommaSepArgs(argc, argv, "dt-sce=", p->filters[7]);
+ parseCommaSepArgs(argc, argv, "dt-reporters=", p->filters[8]);
+ parseCommaSepArgs(argc, argv, "dt-r=", p->filters[8]);
// clang-format on
int intRes = 0;
@@ -5031,8 +5300,8 @@
DOCTEST_PARSE_STR_OPTION("dt-order-by", "dt-ob", order_by, "file");
DOCTEST_PARSE_INT_OPTION("dt-rand-seed", "dt-rs", rand_seed, 0);
- DOCTEST_PARSE_INT_OPTION("dt-first", "dt-f", first, 1);
- DOCTEST_PARSE_INT_OPTION("dt-last", "dt-l", last, 0);
+ DOCTEST_PARSE_INT_OPTION("dt-first", "dt-f", first, 0);
+ DOCTEST_PARSE_INT_OPTION("dt-last", "dt-l", last, UINT_MAX);
DOCTEST_PARSE_INT_OPTION("dt-abort-after", "dt-aa", abort_after, 0);
DOCTEST_PARSE_INT_OPTION("dt-subcase-filter-levels", "dt-scfl", subcase_filter_levels, 2000000000);
@@ -5055,16 +5324,13 @@
DOCTEST_PARSE_AS_BOOL_OR_FLAG("dt-no-skipped-summary", "dt-nss", no_skipped_summary, false);
// clang-format on
-#undef DOCTEST_PARSE_STR_OPTION
-#undef DOCTEST_PARSE_INT_OPTION
-#undef DOCTEST_PARSE_AS_BOOL_OR_FLAG
-
if(withDefaults) {
p->help = false;
p->version = false;
p->count = false;
p->list_test_cases = false;
p->list_test_suites = false;
+ p->list_reporters = false;
}
if(parseFlag(argc, argv, "dt-help") || parseFlag(argc, argv, "dt-h") ||
parseFlag(argc, argv, "dt-?")) {
@@ -5087,6 +5353,10 @@
p->list_test_suites = true;
p->exit = true;
}
+ if(parseFlag(argc, argv, "dt-list-reporters") || parseFlag(argc, argv, "dt-lr")) {
+ p->list_reporters = true;
+ p->exit = true;
+ }
}
// allows the user to add procedurally to the filters from the command line
@@ -5117,33 +5387,46 @@
int Context::run() {
using namespace detail;
- Color::init();
-
- contextState = p;
+ g_contextState = p;
p->resetRunData();
- // handle version, help and no_run
- if(p->no_run || p->version || p->help) {
- if(p->version)
- printVersion(std::cout);
- if(p->help)
- printHelp(std::cout);
+ ConsoleReporterWithHelpers g_con_rep(std::cout);
+ registerReporter("console", 0, &g_con_rep);
- contextState = 0;
+ p->reporters_currently_used.clear();
+ if(p->filters[8].size() == 0)
+ p->reporters_currently_used.push_back(getReporters()[reporterMap::key_type(0, "console")]);
+ for(reporterMap::iterator it = getReporters().begin(); it != getReporters().end(); ++it) {
+ if(matchesAny(it->first.second.c_str(), p->filters[8], false, p->case_sensitive))
+ p->reporters_currently_used.push_back(it->second);
+ }
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(isDebuggerActive())
+ p->reporters_currently_used.push_back(&g_debug_output_rep);
+#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();
+
+ g_contextState = 0;
return EXIT_SUCCESS;
}
- printVersion(std::cout);
- std::cout << Color::Cyan << "[doctest] " << Color::None << "run with \"--help\" for options\n";
+ g_con_rep.printIntro();
- unsigned i = 0; // counter used for loops - here for VC6
-
- std::set<TestCase>& registeredTests = getRegisteredTests();
+ std::set<TestCase>& all_tests = getRegisteredTests();
+ p->numTestCases = all_tests.size();
std::vector<const TestCase*> testArray;
- for(std::set<TestCase>::iterator it = registeredTests.begin(); it != registeredTests.end();
- ++it)
+ for(std::set<TestCase>::iterator it = all_tests.begin(); it != all_tests.end(); ++it)
testArray.push_back(&(*it));
// sort the collected records
@@ -5159,7 +5442,7 @@
// random_shuffle implementation
const TestCase** first = &testArray[0];
- for(i = testArray.size() - 1; i > 0; --i) {
+ for(size_t i = testArray.size() - 1; i > 0; --i) {
int idxToSwap = std::rand() % (i + 1); // NOLINT
const TestCase* temp = first[i];
@@ -5170,38 +5453,52 @@
}
}
- if(p->list_test_cases) {
- std::cout << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n";
- separator_to_stream(std::cout);
- }
+ if(p->list_test_cases)
+ g_con_rep.output_query_preamble_test_cases();
- std::set<String> testSuitesPassingFilters;
- if(p->list_test_suites) {
- std::cout << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
- separator_to_stream(std::cout);
- }
+ std::set<String> testSuitesPassingFilt;
+ if(p->list_test_suites)
+ g_con_rep.output_query_preamble_test_suites();
+
+ bool query_mode = p->count || p->list_test_cases || p->list_test_suites;
+
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, *g_contextState);
// invoke the registered functions if they match the filter criteria (or just count them)
- for(i = 0; i < testArray.size(); i++) {
- const TestCase& data = *testArray[i];
+ for(size_t i = 0; i < testArray.size(); i++) {
+ const TestCase& tc = *testArray[i];
- if(data.m_skip && !p->no_skip)
- continue;
+ bool skip_me = false;
+ if(tc.m_skip && !p->no_skip)
+ skip_me = true;
- if(!matchesAny(data.m_file, p->filters[0], 1, p->case_sensitive))
- continue;
- if(matchesAny(data.m_file, p->filters[1], 0, p->case_sensitive))
- continue;
- if(!matchesAny(data.m_test_suite, p->filters[2], 1, p->case_sensitive))
- continue;
- if(matchesAny(data.m_test_suite, p->filters[3], 0, p->case_sensitive))
- continue;
- if(!matchesAny(data.m_name, p->filters[4], 1, p->case_sensitive))
- continue;
- if(matchesAny(data.m_name, p->filters[5], 0, p->case_sensitive))
- continue;
+ if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive))
+ skip_me = true;
- p->numTestsPassingFilters++;
+ if(!skip_me)
+ p->numTestCasesPassingFilters++;
+
+ // skip the test if it is not in the execution range
+ if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) ||
+ (p->first > p->numTestCasesPassingFilters))
+ skip_me = true;
+
+ if(skip_me) {
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc);
+ continue;
+ }
// do not execute the test if we are to only count the number of filter passing tests
if(p->count)
@@ -5209,152 +5506,143 @@
// print the name of the test and don't execute it
if(p->list_test_cases) {
- std::cout << Color::None << data.m_name << "\n";
+ g_con_rep.output_c_string_with_newline(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((testSuitesPassingFilters.count(data.m_test_suite) == 0) &&
- data.m_test_suite[0] != '\0') {
- std::cout << Color::None << data.m_test_suite << "\n";
- testSuitesPassingFilters.insert(data.m_test_suite);
+ 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);
+ testSuitesPassingFilt.insert(tc.m_test_suite);
p->numTestSuitesPassingFilters++;
}
continue;
}
- // skip the test if it is not in the execution range
- if((p->last < p->numTestsPassingFilters && p->first <= p->last) ||
- (p->first > p->numTestsPassingFilters))
- continue;
-
// execute the test if it passes all the filtering
{
- p->currentTest = &data;
+ p->currentTest = &tc;
- bool failed = false;
- p->hasLoggedCurrentTestStart = false;
- p->numFailedAssertionsForCurrentTestcase = 0;
+ p->failure_flags = TestCaseFailureReason::None;
+ p->numAssertsFailedForCurrentTestCase = 0;
+ p->numAssertsForCurrentTestCase = 0;
+ p->seconds_so_far = 0;
+ p->error_string = "";
+
p->subcasesPassed.clear();
- double duration = 0;
- Timer timer;
- timer.start();
do {
- // if the start has been logged from a previous iteration of this loop
- if(p->hasLoggedCurrentTestStart)
- logTestEnd();
- p->hasLoggedCurrentTestStart = false;
-
- // if logging successful tests - force the start log
- if(p->success)
- DOCTEST_LOG_START;
-
- // reset the assertion state
- p->numAssertionsForCurrentTestcase = 0;
- p->hasCurrentTestFailed = false;
-
// reset some of the fields for subcases (except for the set of fully passed ones)
- p->subcasesHasSkipped = false;
+ p->should_reenter = false;
p->subcasesCurrentLevel = 0;
p->subcasesEnteredLevels.clear();
// reset stuff for logging with INFO()
- p->exceptionalContexts.clear();
+ p->stringifiedContexts.clear();
-// execute the test
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
+
+ g_timer.start();
+
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
try {
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
FatalConditionHandler fatalConditionHandler; // Handle signals
- data.m_test();
+ // execute the test
+ tc.m_test();
fatalConditionHandler.reset();
- if(contextState->hasCurrentTestFailed)
- failed = true;
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
- } catch(const TestFailureException&) { failed = true; } catch(...) {
- DOCTEST_LOG_START;
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(
- logTestException,
- DOCTEST_COMMA * contextState->currentTest DOCTEST_COMMA
- translateActiveException() DOCTEST_COMMA false);
-
- failed = true;
+ } catch(const TestFailureException&) {
+ p->failure_flags |= TestCaseFailureReason::AssertFailure;
+ } catch(...) {
+ p->error_string = translateActiveException();
+ p->failure_flags |= TestCaseFailureReason::Exception;
}
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
- p->numAssertions += p->numAssertionsForCurrentTestcase;
+ p->seconds_so_far += g_timer.getElapsedSeconds();
- // exit this loop if enough assertions have failed
- if(p->abort_after > 0 && p->numFailedAssertions >= p->abort_after) {
- p->subcasesHasSkipped = false;
- std::cout << Color::Red << "Aborting - too many failed asserts!\n";
+ // exit this loop if enough assertions have failed - even if there are more subcases
+ if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) {
+ p->should_reenter = false;
+ p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
}
- } while(p->subcasesHasSkipped == true);
+ // 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_contextState);
+ } while(p->should_reenter == true);
- duration = timer.getElapsedSeconds();
+ if(g_contextState->numAssertsFailedForCurrentTestCase)
+ p->failure_flags |= TestCaseFailureReason::AssertFailure;
if(Approx(p->currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
- Approx(duration).epsilon(DBL_EPSILON) > p->currentTest->m_timeout) {
- failed = true;
- DOCTEST_LOG_START;
- std::cout << Color::Red << "Test case exceeded time limit of "
- << std::setprecision(6) << std::fixed << p->currentTest->m_timeout
- << "!\n";
- }
+ Approx(p->seconds_so_far).epsilon(DBL_EPSILON) > p->currentTest->m_timeout)
+ p->failure_flags |= TestCaseFailureReason::Timeout;
- if(p->duration)
- std::cout << Color::None << std::setprecision(6) << std::fixed << duration
- << " s: " << p->currentTest->m_name << "\n";
-
- if(data.m_should_fail) {
- DOCTEST_LOG_START;
- if(failed)
- std::cout << Color::Yellow
- << "Failed as expected so marking it as not failed\n";
- else
- std::cout << Color::Red
- << "Should have failed but didn't! Marking it as failed!\n";
- failed = !failed;
- } else if(failed && data.m_may_fail) {
- DOCTEST_LOG_START;
- failed = false;
- std::cout << Color::Yellow << "Allowed to fail so marking it as not failed\n";
- } else if(data.m_expected_failures > 0) {
- DOCTEST_LOG_START;
- if(p->numFailedAssertionsForCurrentTestcase == data.m_expected_failures) {
- failed = false;
- std::cout << Color::Yellow << "Failed exactly " << data.m_expected_failures
- << " times as expected so marking it as not failed!\n";
+ if(tc.m_should_fail) {
+ if(p->failure_flags) {
+ p->failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
} else {
- failed = true;
- std::cout << Color::Red << "Didn't fail exactly " << data.m_expected_failures
- << " times so marking it as failed!\n";
+ 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;
}
}
- std::cout << Color::None;
- if(p->hasLoggedCurrentTestStart)
- logTestEnd();
+ bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & p->failure_flags) ||
+ (TestCaseFailureReason::CouldHaveFailedAndDid & p->failure_flags) ||
+ (TestCaseFailureReason::FailedExactlyNumTimes & p->failure_flags);
- if(failed) // if any subcase has failed - the whole test case has failed
- p->numFailed++;
+ // if any subcase has failed - the whole test case has failed
+ if(p->failure_flags && !ok_to_fail)
+ p->numTestCasesFailed++;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_contextState);
// stop executing tests if enough assertions have failed
- if(p->abort_after > 0 && p->numFailedAssertions >= p->abort_after)
+ if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after)
break;
}
}
- printSummary(std::cout);
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_contextState);
+ else
+ g_con_rep.output_query_results();
- contextState = 0;
+ g_contextState = 0;
- if(p->numFailed && !p->no_exitcode)
+ if(p->numTestCasesFailed && !p->no_exitcode)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
+
+int IReporter::get_num_active_contexts() { return detail::g_contextState->contexts.size(); }
+const IContextScope* const* IReporter::get_active_contexts() {
+ return get_num_active_contexts() ? &detail::g_contextState->contexts[0] : 0;
+}
+
+int IReporter::get_num_stringified_contexts() {
+ return detail::g_contextState->stringifiedContexts.size();
+}
+const String* IReporter::get_stringified_contexts() {
+ return get_num_stringified_contexts() ? &detail::g_contextState->stringifiedContexts[0] : 0;
+}
+
+int registerReporter(const char* name, int priority, IReporter* r) {
+ detail::getReporters().insert(
+ detail::reporterMap::value_type(detail::reporterMap::key_type(priority, name), r));
+ return 0;
+}
+
} // namespace doctest
#endif // DOCTEST_CONFIG_DISABLE
diff --git a/doctest/parts/doctest_fwd.h b/doctest/parts/doctest_fwd.h
index 9905065..c3d2ea2 100644
--- a/doctest/parts/doctest_fwd.h
+++ b/doctest/parts/doctest_fwd.h
@@ -207,6 +207,11 @@
DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly defined as deleted
DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
+// static analysis
+DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
+DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
+DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
+DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr...
// C4548 - expression before comma has no effect; expected expression with side - effect
// C4986 - exception specification does not match previous declaration
@@ -263,8 +268,12 @@
#ifndef DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
+#ifndef DOCTEST_CONFIG_WITH_OVERRIDE
+#define DOCTEST_CONFIG_WITH_OVERRIDE
+#endif // DOCTEST_CONFIG_WITH_OVERRIDE
#endif // __cplusplus >= 201103L
+// general compiler feature support table: https://en.cppreference.com/w/cpp/compiler_support
// 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:
@@ -466,6 +475,12 @@
#define DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK 5
#endif // DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK
+#ifdef DOCTEST_CONFIG_WITH_OVERRIDE
+#define DOCTEST_OVERRIDE override
+#else // DOCTEST_CONFIG_WITH_OVERRIDE
+#define DOCTEST_OVERRIDE
+#endif // DOCTEST_CONFIG_WITH_OVERRIDE
+
// =================================================================================================
// == FEATURE DETECTION END ========================================================================
// =================================================================================================
@@ -516,6 +531,12 @@
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") static int var DOCTEST_UNUSED
#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#ifdef DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_disabled
+#else // DOCTEST_CONFIG_DISABLE
+#define DOCTEST_BRANCH_ON_DISABLED(if_disabled, if_not_disabled) if_not_disabled
+#endif // DOCTEST_CONFIG_DISABLE
+
// should probably take a look at https://github.com/scottt/debugbreak
#ifdef DOCTEST_PLATFORM_MAC
#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
@@ -533,12 +554,11 @@
#include <ciso646>
#endif // clang
-#ifdef _LIBCPP_VERSION
+#if defined(_LIBCPP_VERSION) || defined(DOCTEST_CONFIG_USE_IOSFWD)
// not forward declaring ostream for libc++ because I had some problems (inline namespaces vs c++98)
// so the <iosfwd> header is used - also it is very light and doesn't drag a ton of stuff
#include <iosfwd>
-#else // _LIBCPP_VERSION
-#ifndef DOCTEST_CONFIG_USE_IOSFWD
+#else // _LIBCPP_VERSION
namespace std
{
template <class charT>
@@ -549,10 +569,7 @@
class basic_ostream;
typedef basic_ostream<char, char_traits<char> > ostream;
} // namespace std
-#else // DOCTEST_CONFIG_USE_IOSFWD
-#include <iosfwd>
-#endif // DOCTEST_CONFIG_USE_IOSFWD
-#endif // _LIBCPP_VERSION
+#endif // _LIBCPP_VERSION || DOCTEST_CONFIG_USE_IOSFWD
// static assert macro - because of the c++98 support requires that the message is an
// identifier (no spaces and not a C string) - example without quotes: I_am_a_message
@@ -584,8 +601,6 @@
#endif // _LIBCPP_VERSION
#endif // DOCTEST_CONFIG_WITH_NULLPTR
-#ifndef DOCTEST_CONFIG_DISABLE
-
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#include <type_traits>
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
@@ -594,48 +609,10 @@
{
namespace detail
{
- struct TestSuite
- {
- const char* m_test_suite;
- const char* m_description;
- bool m_skip;
- bool m_may_fail;
- bool m_should_fail;
- int m_expected_failures;
- double m_timeout;
-
- TestSuite& operator*(const char* in) {
- m_test_suite = in;
- // clear state
- m_description = 0;
- m_skip = false;
- m_may_fail = false;
- m_should_fail = false;
- m_expected_failures = 0;
- m_timeout = 0;
- return *this;
- }
-
- template <typename T>
- TestSuite& operator*(const T& in) {
- in.fill(*this);
- return *this;
- }
- };
+ // the function type this library works with
+ typedef void (*funcType)();
} // namespace detail
-} // namespace doctest
-// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
-// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
-namespace doctest_detail_test_suite_ns
-{
-DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
-} // namespace doctest_detail_test_suite_ns
-
-#endif // DOCTEST_CONFIG_DISABLE
-
-namespace doctest
-{
// A 24 byte string class (can be as small as 17 for x64 and 13 for x86) that can hold strings with length
// of up to 23 chars on the stack before going on the heap - the last byte of the buffer is used for:
// - "is small" bit - the highest bit - if "0" then it is small - otherwise its "1" (128)
@@ -760,6 +737,251 @@
DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, const String& in);
+namespace Color
+{
+ enum Enum
+ {
+ None = 0,
+ White,
+ Red,
+ Green,
+ Blue,
+ Cyan,
+ Yellow,
+ Grey,
+
+ Bright = 0x10,
+
+ BrightRed = Bright | Red,
+ BrightGreen = Bright | Green,
+ LightGrey = Bright | Grey,
+ BrightWhite = Bright | White
+ };
+
+ DOCTEST_INTERFACE std::ostream& operator<<(std::ostream& s, Color::Enum code);
+} // namespace Color
+
+namespace assertType
+{
+ enum Enum
+ {
+ // macro traits
+
+ is_warn = 1,
+ is_check = 2,
+ is_require = 4,
+
+ is_throws = 8,
+ is_throws_as = 16,
+ is_nothrow = 32,
+
+ is_fast = 64, // not checked anywhere - used just to distinguish the types
+ is_false = 128,
+ is_unary = 256,
+
+ is_eq = 512,
+ is_ne = 1024,
+
+ is_lt = 2048,
+ is_gt = 4096,
+
+ is_ge = 8192,
+ is_le = 16384,
+
+ // macro types
+
+ DT_WARN = is_warn,
+ DT_CHECK = is_check,
+ DT_REQUIRE = is_require,
+
+ DT_WARN_FALSE = is_false | is_warn,
+ DT_CHECK_FALSE = is_false | is_check,
+ DT_REQUIRE_FALSE = is_false | is_require,
+
+ DT_WARN_THROWS = is_throws | is_warn,
+ DT_CHECK_THROWS = is_throws | is_check,
+ DT_REQUIRE_THROWS = is_throws | is_require,
+
+ DT_WARN_THROWS_AS = is_throws_as | is_warn,
+ DT_CHECK_THROWS_AS = is_throws_as | is_check,
+ DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
+
+ DT_WARN_NOTHROW = is_nothrow | is_warn,
+ DT_CHECK_NOTHROW = is_nothrow | is_check,
+ DT_REQUIRE_NOTHROW = is_nothrow | is_require,
+
+ DT_WARN_EQ = is_eq | is_warn,
+ DT_CHECK_EQ = is_eq | is_check,
+ DT_REQUIRE_EQ = is_eq | is_require,
+
+ DT_WARN_NE = is_ne | is_warn,
+ DT_CHECK_NE = is_ne | is_check,
+ DT_REQUIRE_NE = is_ne | is_require,
+
+ DT_WARN_GT = is_gt | is_warn,
+ DT_CHECK_GT = is_gt | is_check,
+ DT_REQUIRE_GT = is_gt | is_require,
+
+ DT_WARN_LT = is_lt | is_warn,
+ DT_CHECK_LT = is_lt | is_check,
+ DT_REQUIRE_LT = is_lt | is_require,
+
+ DT_WARN_GE = is_ge | is_warn,
+ DT_CHECK_GE = is_ge | is_check,
+ DT_REQUIRE_GE = is_ge | is_require,
+
+ DT_WARN_LE = is_le | is_warn,
+ DT_CHECK_LE = is_le | is_check,
+ DT_REQUIRE_LE = is_le | is_require,
+
+ DT_WARN_UNARY = is_unary | is_warn,
+ DT_CHECK_UNARY = is_unary | is_check,
+ DT_REQUIRE_UNARY = is_unary | is_require,
+
+ DT_WARN_UNARY_FALSE = is_false | is_unary | is_warn,
+ DT_CHECK_UNARY_FALSE = is_false | is_unary | is_check,
+ DT_REQUIRE_UNARY_FALSE = is_false | is_unary | is_require,
+
+ DT_FAST_WARN_EQ = is_fast | is_eq | is_warn,
+ DT_FAST_CHECK_EQ = is_fast | is_eq | is_check,
+ DT_FAST_REQUIRE_EQ = is_fast | is_eq | is_require,
+
+ DT_FAST_WARN_NE = is_fast | is_ne | is_warn,
+ DT_FAST_CHECK_NE = is_fast | is_ne | is_check,
+ DT_FAST_REQUIRE_NE = is_fast | is_ne | is_require,
+
+ DT_FAST_WARN_GT = is_fast | is_gt | is_warn,
+ DT_FAST_CHECK_GT = is_fast | is_gt | is_check,
+ DT_FAST_REQUIRE_GT = is_fast | is_gt | is_require,
+
+ DT_FAST_WARN_LT = is_fast | is_lt | is_warn,
+ DT_FAST_CHECK_LT = is_fast | is_lt | is_check,
+ DT_FAST_REQUIRE_LT = is_fast | is_lt | is_require,
+
+ DT_FAST_WARN_GE = is_fast | is_ge | is_warn,
+ DT_FAST_CHECK_GE = is_fast | is_ge | is_check,
+ DT_FAST_REQUIRE_GE = is_fast | is_ge | is_require,
+
+ DT_FAST_WARN_LE = is_fast | is_le | is_warn,
+ DT_FAST_CHECK_LE = is_fast | is_le | is_check,
+ DT_FAST_REQUIRE_LE = is_fast | is_le | is_require,
+
+ DT_FAST_WARN_UNARY = is_fast | is_unary | is_warn,
+ DT_FAST_CHECK_UNARY = is_fast | is_unary | is_check,
+ DT_FAST_REQUIRE_UNARY = is_fast | is_unary | is_require,
+
+ DT_FAST_WARN_UNARY_FALSE = is_fast | is_false | is_unary | is_warn,
+ DT_FAST_CHECK_UNARY_FALSE = is_fast | is_false | is_unary | is_check,
+ DT_FAST_REQUIRE_UNARY_FALSE = is_fast | is_false | is_unary | is_require
+ };
+} // namespace assertType
+
+DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
+
+struct TestCaseData
+{
+ const char* m_file; // the file in which the test was registered
+ unsigned m_line; // the line where the test was registered
+ const char* m_name; // name of the test case
+ const char* m_test_suite; // the test suite in which the test was added
+ const char* m_description;
+ bool m_skip;
+ bool m_may_fail;
+ bool m_should_fail;
+ int m_expected_failures;
+ double m_timeout;
+};
+
+struct AssertData
+{
+ // common - for all asserts
+ const TestCaseData* m_test_case;
+ assertType::Enum m_at;
+ const char* m_file;
+ int m_line;
+ const char* m_expr;
+ bool m_failed;
+
+ // exception-related - for all asserts
+ bool m_threw;
+ String m_exception;
+
+ // for normal asserts
+ String m_decomposition;
+
+ // for specific exception-related asserts
+ bool m_threw_as;
+ const char* m_exception_type;
+};
+
+struct MessageData
+{
+ String m_string;
+ const char* m_file;
+ int m_line;
+ assertType::Enum m_severity;
+
+ // for gcc 4.8
+ DOCTEST_NOINLINE ~MessageData() {}
+};
+
+struct DOCTEST_INTERFACE SubcaseSignature
+{
+ const char* m_name;
+ const char* m_file;
+ int m_line;
+
+ SubcaseSignature(const char* name, const char* file, int line)
+ : m_name(name)
+ , m_file(file)
+ , m_line(line) {}
+
+ bool operator<(const SubcaseSignature& other) const;
+};
+
+struct IContextScope
+{
+ virtual ~IContextScope() {}
+ virtual void stringify(std::ostream*) const = 0;
+};
+
+struct ContextOptions //!OCLINT too many fields
+{
+ // == parameters from the command line
+ String order_by; // how tests should be ordered
+ unsigned rand_seed; // the seed for rand ordering
+
+ unsigned first; // the first (matching) test to be executed
+ unsigned last; // the last (matching) test to be executed
+
+ int abort_after; // stop tests after this many failed assertions
+ int subcase_filter_levels; // apply the subcase filters for the first N levels
+
+ bool success; // include successful assertions in output
+ bool case_sensitive; // if filtering should be case sensitive
+ bool exit; // if the program should be exited after the tests are ran/whatever
+ bool duration; // print the time duration of each test case
+ bool no_throw; // to skip exceptions-related assertion macros
+ bool no_exitcode; // if the framework should return 0 as the exitcode
+ bool no_run; // to not run the tests at all (can be done with an "*" exclude)
+ bool no_version; // to not print the version of the framework
+ bool no_colors; // if output to the console should be colorized
+ bool force_colors; // forces the use of colors even when a tty cannot be detected
+ bool no_breaks; // to not break into the debugger
+ bool no_skip; // don't skip test cases which are marked to be skipped
+ bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x):
+ bool no_path_in_filenames; // if the path to files should be removed from the output
+ bool no_line_numbers; // if source code line numbers should be omitted from the output
+ bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
+
+ bool help; // to print the help
+ bool version; // to print the version
+ bool count; // if only the count of matching tests is to be retreived
+ bool list_test_cases; // to list all tests matching the filters
+ bool list_test_suites; // to list all suites matching the filters
+ bool list_reporters; // lists all registered reporters
+};
+
namespace detail
{
#ifndef DOCTEST_CONFIG_WITH_STATIC_ASSERT
@@ -1126,126 +1348,6 @@
namespace detail
{
- // the function type this library works with
- typedef void (*funcType)();
-
- namespace assertType
- {
- enum Enum
- {
- // macro traits
-
- is_warn = 1,
- is_check = 2,
- is_require = 4,
-
- is_throws = 8,
- is_throws_as = 16,
- is_nothrow = 32,
-
- is_fast = 64, // not checked anywhere - used just to distinguish the types
- is_false = 128,
- is_unary = 256,
-
- is_eq = 512,
- is_ne = 1024,
-
- is_lt = 2048,
- is_gt = 4096,
-
- is_ge = 8192,
- is_le = 16384,
-
- // macro types
-
- DT_WARN = is_warn,
- DT_CHECK = is_check,
- DT_REQUIRE = is_require,
-
- DT_WARN_FALSE = is_false | is_warn,
- DT_CHECK_FALSE = is_false | is_check,
- DT_REQUIRE_FALSE = is_false | is_require,
-
- DT_WARN_THROWS = is_throws | is_warn,
- DT_CHECK_THROWS = is_throws | is_check,
- DT_REQUIRE_THROWS = is_throws | is_require,
-
- DT_WARN_THROWS_AS = is_throws_as | is_warn,
- DT_CHECK_THROWS_AS = is_throws_as | is_check,
- DT_REQUIRE_THROWS_AS = is_throws_as | is_require,
-
- DT_WARN_NOTHROW = is_nothrow | is_warn,
- DT_CHECK_NOTHROW = is_nothrow | is_check,
- DT_REQUIRE_NOTHROW = is_nothrow | is_require,
-
- DT_WARN_EQ = is_eq | is_warn,
- DT_CHECK_EQ = is_eq | is_check,
- DT_REQUIRE_EQ = is_eq | is_require,
-
- DT_WARN_NE = is_ne | is_warn,
- DT_CHECK_NE = is_ne | is_check,
- DT_REQUIRE_NE = is_ne | is_require,
-
- DT_WARN_GT = is_gt | is_warn,
- DT_CHECK_GT = is_gt | is_check,
- DT_REQUIRE_GT = is_gt | is_require,
-
- DT_WARN_LT = is_lt | is_warn,
- DT_CHECK_LT = is_lt | is_check,
- DT_REQUIRE_LT = is_lt | is_require,
-
- DT_WARN_GE = is_ge | is_warn,
- DT_CHECK_GE = is_ge | is_check,
- DT_REQUIRE_GE = is_ge | is_require,
-
- DT_WARN_LE = is_le | is_warn,
- DT_CHECK_LE = is_le | is_check,
- DT_REQUIRE_LE = is_le | is_require,
-
- DT_WARN_UNARY = is_unary | is_warn,
- DT_CHECK_UNARY = is_unary | is_check,
- DT_REQUIRE_UNARY = is_unary | is_require,
-
- DT_WARN_UNARY_FALSE = is_false | is_unary | is_warn,
- DT_CHECK_UNARY_FALSE = is_false | is_unary | is_check,
- DT_REQUIRE_UNARY_FALSE = is_false | is_unary | is_require,
-
- DT_FAST_WARN_EQ = is_fast | is_eq | is_warn,
- DT_FAST_CHECK_EQ = is_fast | is_eq | is_check,
- DT_FAST_REQUIRE_EQ = is_fast | is_eq | is_require,
-
- DT_FAST_WARN_NE = is_fast | is_ne | is_warn,
- DT_FAST_CHECK_NE = is_fast | is_ne | is_check,
- DT_FAST_REQUIRE_NE = is_fast | is_ne | is_require,
-
- DT_FAST_WARN_GT = is_fast | is_gt | is_warn,
- DT_FAST_CHECK_GT = is_fast | is_gt | is_check,
- DT_FAST_REQUIRE_GT = is_fast | is_gt | is_require,
-
- DT_FAST_WARN_LT = is_fast | is_lt | is_warn,
- DT_FAST_CHECK_LT = is_fast | is_lt | is_check,
- DT_FAST_REQUIRE_LT = is_fast | is_lt | is_require,
-
- DT_FAST_WARN_GE = is_fast | is_ge | is_warn,
- DT_FAST_CHECK_GE = is_fast | is_ge | is_check,
- DT_FAST_REQUIRE_GE = is_fast | is_ge | is_require,
-
- DT_FAST_WARN_LE = is_fast | is_le | is_warn,
- DT_FAST_CHECK_LE = is_fast | is_le | is_check,
- DT_FAST_REQUIRE_LE = is_fast | is_le | is_require,
-
- DT_FAST_WARN_UNARY = is_fast | is_unary | is_warn,
- DT_FAST_CHECK_UNARY = is_fast | is_unary | is_check,
- DT_FAST_REQUIRE_UNARY = is_fast | is_unary | is_require,
-
- DT_FAST_WARN_UNARY_FALSE = is_fast | is_false | is_unary | is_warn,
- DT_FAST_CHECK_UNARY_FALSE = is_fast | is_false | is_unary | is_check,
- DT_FAST_REQUIRE_UNARY_FALSE = is_fast | is_false | is_unary | is_require
- };
- } // namespace assertType
-
- DOCTEST_INTERFACE const char* assertString(assertType::Enum at);
-
// clang-format off
template<class T> struct decay_array { typedef T type; };
template<class T, unsigned N> struct decay_array<T[N]> { typedef T* type; };
@@ -1265,29 +1367,7 @@
DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at);
DOCTEST_INTERFACE void fastAssertThrowIfFlagSet(int flags);
- struct TestAccessibleContextState
- {
- bool no_throw; // to skip exceptions-related assertion macros
- bool success; // include successful assertions in output
- };
-
- struct ContextState;
-
- DOCTEST_INTERFACE TestAccessibleContextState* getTestsContextState();
-
- struct DOCTEST_INTERFACE SubcaseSignature
- {
- const char* m_name;
- const char* m_file;
- int m_line;
-
- SubcaseSignature(const char* name, const char* file, int line)
- : m_name(name)
- , m_file(file)
- , m_line(line) {}
-
- bool operator<(const SubcaseSignature& other) const;
- };
+ DOCTEST_INTERFACE const ContextOptions* getContextOptions();
// cppcheck-suppress copyCtorAndEqOperator
struct DOCTEST_INTERFACE Subcase
@@ -1422,7 +1502,7 @@
bool res = op_macro(lhs, rhs); \
if(m_at & assertType::is_false) \
res = !res; \
- if(!res || doctest::detail::getTestsContextState()->success) \
+ if(!res || doctest::detail::getContextOptions()->success) \
return Result(res, stringifyBinaryExpr(lhs, op_str, rhs)); \
return Result(res); \
}
@@ -1451,7 +1531,7 @@
if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
res = !res;
- if(!res || getTestsContextState()->success)
+ if(!res || getContextOptions()->success)
return Result(res, toString(lhs));
return Result(res);
}
@@ -1513,14 +1593,9 @@
}
};
- struct DOCTEST_INTERFACE TestCase
+ struct TestSuite
{
- // not used for determining uniqueness
- funcType m_test; // a function pointer to the test case
- String m_full_name; // contains the name (only for templated test cases!) + the template type
- const char* m_name; // name of the test case
- const char* m_type; // for templated test cases - gets appended to the real name
- const char* m_test_suite; // the test suite in which the test was added
+ const char* m_test_suite;
const char* m_description;
bool m_skip;
bool m_may_fail;
@@ -1528,10 +1603,32 @@
int m_expected_failures;
double m_timeout;
- // fields by which uniqueness of test cases shall be determined
- const char* m_file; // the file in which the test was registered
- unsigned m_line; // the line where the test was registered
+ TestSuite& operator*(const char* in) {
+ m_test_suite = in;
+ // clear state
+ m_description = 0;
+ m_skip = false;
+ m_may_fail = false;
+ m_should_fail = false;
+ m_expected_failures = 0;
+ m_timeout = 0;
+ return *this;
+ }
+
+ template <typename T>
+ TestSuite& operator*(const T& in) {
+ in.fill(*this);
+ return *this;
+ }
+ };
+
+ struct DOCTEST_INTERFACE TestCase : public TestCaseData
+ {
+ detail::funcType m_test; // a function pointer to the test case
+
+ const char* m_type; // for templated test cases - gets appended to the real name
int m_template_id; // an ID used to distinguish between the different versions of a templated test case
+ String m_full_name; // contains the name (only for templated test cases!) + the template type
TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
const char* type = "", int template_id = -1);
@@ -1549,7 +1646,9 @@
TestCase(const TestCase& other) { *this = other; }
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26434) // hides a non-virtual function
TestCase& operator=(const TestCase& other);
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
bool operator<(const TestCase& other) const;
};
@@ -1581,51 +1680,35 @@
template <class L, class R> struct RelationalComparator<5, L, R> { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return le(lhs, rhs); } };
// clang-format on
- struct DOCTEST_INTERFACE ResultBuilder
+ struct DOCTEST_INTERFACE ResultBuilder : public AssertData
{
- // common - for all asserts
- const TestCase& m_test_case;
- assertType::Enum m_at;
- const char* m_file;
- int m_line;
- const char* m_expr;
- bool m_failed;
-
- // exception-related - for all asserts
- bool m_threw;
- String m_exception;
-
- // for normal asserts
- Result m_result;
-
- // for specific exception-related asserts
- bool m_threw_as;
- const char* m_exception_type;
-
ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
const char* exception_type = "");
~ResultBuilder();
- void setResult(const Result& res) { m_result = res; }
+ void setResult(const Result& res) {
+ m_decomposition = res.m_decomposition;
+ m_failed = !res.m_passed;
+ }
template <int comparison, typename L, typename R>
DOCTEST_NOINLINE void binary_assert(const DOCTEST_REF_WRAP(L) lhs,
const DOCTEST_REF_WRAP(R) rhs) {
- m_result.m_passed = RelationalComparator<comparison, L, R>()(lhs, rhs);
- if(!m_result.m_passed || getTestsContextState()->success)
- m_result.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
+ m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
+ if(m_failed || getContextOptions()->success)
+ m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
}
template <typename L>
DOCTEST_NOINLINE void unary_assert(const DOCTEST_REF_WRAP(L) val) {
- m_result.m_passed = !!val;
+ m_failed = !val;
if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
- m_result.m_passed = !m_result.m_passed;
+ m_failed = !m_failed;
- if(!m_result.m_passed || getTestsContextState()->success)
- m_result.m_decomposition = toString(val);
+ if(m_failed || getContextOptions()->success)
+ m_decomposition = toString(val);
}
void unexpectedExceptionOccurred();
@@ -1650,10 +1733,10 @@
const DOCTEST_REF_WRAP(R) rhs) {
ResultBuilder rb(at, file, line, expr);
- rb.m_result.m_passed = RelationalComparator<comparison, L, R>()(lhs, rhs);
+ rb.m_failed = !RelationalComparator<comparison, L, R>()(lhs, rhs);
- if(!rb.m_result.m_passed || getTestsContextState()->success)
- rb.m_result.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
+ if(rb.m_failed || getContextOptions()->success)
+ rb.m_decomposition = stringifyBinaryExpr(lhs, ", ", rhs);
int res = 0;
@@ -1681,13 +1764,13 @@
const char* val_str, const DOCTEST_REF_WRAP(L) val) {
ResultBuilder rb(at, file, line, val_str);
- rb.m_result.m_passed = !!val;
+ rb.m_failed = !val;
if(at & assertType::is_false) //!OCLINT bitwise operator in conditional
- rb.m_result.m_passed = !rb.m_result.m_passed;
+ rb.m_failed = !rb.m_failed;
- if(!rb.m_result.m_passed || getTestsContextState()->success)
- rb.m_result.m_decomposition = toString(val);
+ if(rb.m_failed || getContextOptions()->success)
+ rb.m_decomposition = toString(val);
int res = 0;
@@ -1803,15 +1886,9 @@
DOCTEST_INTERFACE void toStream(std::ostream* s, int long long unsigned in);
#endif // DOCTEST_CONFIG_WITH_LONG_LONG
- struct IContextScope
- {
- virtual ~IContextScope() {}
- virtual void build(std::ostream*) = 0;
- };
-
DOCTEST_INTERFACE void addToContexts(IContextScope* ptr);
DOCTEST_INTERFACE void popFromContexts();
- DOCTEST_INTERFACE void useContextIfExceptionOccurred(IContextScope* ptr);
+ DOCTEST_INTERFACE void stringifyContextIfExceptionOccurred(IContextScope* ptr);
// cppcheck-suppress copyCtorAndEqOperator
class ContextBuilder
@@ -1831,9 +1908,7 @@
explicit Capture(const T* in)
: capture(in) {}
- virtual void toStream(std::ostream* s) const { // override
- detail::toStream(s, *capture);
- }
+ void toStream(std::ostream* s) const DOCTEST_OVERRIDE { detail::toStream(s, *capture); }
};
struct Chunk
@@ -1854,7 +1929,7 @@
Node* tail;
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wcast-align")
- void build(std::ostream* s) const {
+ void stringify(std::ostream* s) const {
int curr = 0;
// iterate over small buffer
while(curr < numCaptures && curr < DOCTEST_CONFIG_NUM_CAPTURES_ON_STACK)
@@ -1947,21 +2022,17 @@
}
DOCTEST_NOINLINE ~ContextScope() {
- useContextIfExceptionOccurred(this);
+ stringifyContextIfExceptionOccurred(this);
popFromContexts();
}
- void build(std::ostream* s) { contextBuilder.build(s); }
+ void stringify(std::ostream* s) const { contextBuilder.stringify(s); }
};
- class DOCTEST_INTERFACE MessageBuilder
+ struct DOCTEST_INTERFACE MessageBuilder : public MessageData
{
- std::ostream* m_stream;
- const char* m_file;
- int m_line;
- assertType::Enum m_severity;
+ std::ostream* m_stream;
- public:
MessageBuilder(const char* file, int line, assertType::Enum severity);
~MessageBuilder();
@@ -1971,7 +2042,6 @@
return *this;
}
- void log(std::ostream&);
bool log();
void react();
};
@@ -1995,9 +2065,6 @@
DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);
DOCTEST_DEFINE_DECORATOR(expected_failures, int, 0);
-#endif // DOCTEST_CONFIG_DISABLE
-
-#ifndef DOCTEST_CONFIG_DISABLE
template <typename T>
int registerExceptionTranslator(String (*translateFunction)(T)) {
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors")
@@ -2007,6 +2074,17 @@
return 0;
}
+} // namespace doctest
+
+// in a separate namespace outside of doctest because the DOCTEST_TEST_SUITE macro
+// introduces an anonymous namespace in which getCurrentTestSuite gets overridden
+namespace doctest_detail_test_suite_ns
+{
+DOCTEST_INTERFACE doctest::detail::TestSuite& getCurrentTestSuite();
+} // namespace doctest_detail_test_suite_ns
+
+namespace doctest
+{
#else // DOCTEST_CONFIG_DISABLE
template <typename T>
int registerExceptionTranslator(String (*)(T)) {
@@ -2016,16 +2094,18 @@
DOCTEST_INTERFACE bool isRunningInTest();
+namespace detail
+{
+ struct ContextState;
+} // namespace detail
+
// cppcheck-suppress noCopyConstructor
class DOCTEST_INTERFACE Context
{
-#if !defined(DOCTEST_CONFIG_DISABLE)
detail::ContextState* p;
void parseArgs(int argc, const char* const* argv, bool withDefaults = false);
-#endif // DOCTEST_CONFIG_DISABLE
-
public:
explicit Context(int argc = 0, const char* const* argv = 0);
@@ -2043,6 +2123,85 @@
int run();
};
+namespace TestCaseFailureReason
+{
+ enum Enum
+ {
+ None = 0,
+ AssertFailure = 1, // an assertion has failed in the test case
+ Exception = 2, // test case threw an exception
+ Crash = 4, // a crash...
+ TooManyFailedAsserts = 8, // the abort-after option
+ Timeout = 16, // see the timeout decorator
+ ShouldHaveFailedButDidnt = 32, // see the should_fail decorator
+ ShouldHaveFailedAndDid = 64, // see the should_fail decorator
+ DidntFailExactlyNumTimes = 128, // see the expected_failures decorator
+ FailedExactlyNumTimes = 256, // see the expected_failures decorator
+ CouldHaveFailedAndDid = 512 // see the may_fail decorator
+ };
+} // namespace TestCaseFailureReason
+
+struct CurrentTestCaseStats
+{
+ int numAssertsForCurrentTestCase;
+ int numAssertsFailedForCurrentTestCase;
+ double seconds_so_far;
+ int failure_flags; // use TestCaseFailureReason::Enum
+ String error_string;
+ bool should_reenter; // means we are not done with the test case because of subcases
+};
+
+struct TestRunStats
+{
+ unsigned numTestCases;
+ unsigned numTestCasesPassingFilters;
+ unsigned numTestSuitesPassingFilters;
+ unsigned numTestCasesFailed;
+ int numAsserts;
+ int numAssertsFailed;
+};
+
+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;
+ // 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)
+ virtual void test_case_end(const CurrentTestCaseStats&) = 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;
+
+ // called for each assert (don't cache pointers to the input)
+ virtual void log_assert(const AssertData&) = 0;
+ // called for each message (don't cache pointers to the input)
+ virtual void log_message(const MessageData&) = 0;
+
+ // called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
+ // or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
+ virtual void test_case_skipped(const TestCaseData&) = 0;
+
+ // doctest will not be managing the lifetimes of reporters given to it but this would still be nice to have
+ virtual ~IReporter() {}
+
+ // can obtain all currently active contexts and stringify them if one wishes to do so
+ static int get_num_active_contexts();
+ static const IContextScope* const* get_active_contexts();
+
+ // can iterate through contexts which have been stringified automatically in their destructors when an exception has been thrown
+ static int get_num_stringified_contexts();
+ static const String* get_stringified_contexts();
+};
+
+int registerReporter(const char* name, int priority, IReporter* r);
+
} // namespace doctest
// if registering is not disabled
@@ -2276,6 +2435,12 @@
DOCTEST_REGISTER_EXCEPTION_TRANSLATOR_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_), \
signature)
+// for registering
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter) \
+ DOCTEST_GLOBAL_NO_WARNINGS(DOCTEST_ANONYMOUS(_DOCTEST_ANON_REPORTER_)) = \
+ doctest::registerReporter(name, priority, reporter); \
+ DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_)
+
// for logging
#define DOCTEST_INFO(x) \
doctest::detail::ContextScope DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_)( \
@@ -2284,7 +2449,7 @@
#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \
do { \
- doctest::detail::MessageBuilder mb(file, line, doctest::detail::assertType::type); \
+ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \
mb << x; \
DOCTEST_ASSERT_LOG_AND_REACT(mb); \
} while((void)0, 0)
@@ -2314,10 +2479,10 @@
#define DOCTEST_ASSERT_IMPLEMENT_2(expr, assert_type) \
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Woverloaded-shift-op-parentheses") \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \
DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.setResult( \
- doctest::detail::ExpressionDecomposer(doctest::detail::assertType::assert_type) \
+ doctest::detail::ExpressionDecomposer(doctest::assertType::assert_type) \
<< DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \
DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB) \
DOCTEST_CLANG_SUPPRESS_WARNING_POP
@@ -2363,9 +2528,9 @@
#define DOCTEST_ASSERT_THROWS(expr, assert_type) \
do { \
- if(!doctest::detail::getTestsContextState()->no_throw) { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \
- __FILE__, __LINE__, #expr); \
+ if(!doctest::detail::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #expr); \
try { \
expr; \
} catch(...) { _DOCTEST_RB.m_threw = true; } \
@@ -2375,9 +2540,9 @@
#define DOCTEST_ASSERT_THROWS_AS(expr, as, assert_type) \
do { \
- if(!doctest::detail::getTestsContextState()->no_throw) { \
+ if(!doctest::detail::getContextOptions()->no_throw) { \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, #expr, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #expr, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(as))); \
try { \
expr; \
@@ -2391,9 +2556,9 @@
#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \
do { \
- if(!doctest::detail::getTestsContextState()->no_throw) { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \
- __FILE__, __LINE__, #expr); \
+ if(!doctest::detail::getContextOptions()->no_throw) { \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #expr); \
try { \
expr; \
} catch(...) { _DOCTEST_RB.unexpectedExceptionOccurred(); } \
@@ -2437,7 +2602,7 @@
#define DOCTEST_BINARY_ASSERT(assert_type, expr, comp) \
do { \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \
DOCTEST_WRAP_IN_TRY( \
_DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>( \
@@ -2447,8 +2612,8 @@
#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_BINARY_ASSERT(assert_type, lhs, rhs, comp) \
do { \
- doctest::detail::ResultBuilder _DOCTEST_RB(doctest::detail::assertType::assert_type, \
- __FILE__, __LINE__, #lhs ", " #rhs); \
+ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #lhs ", " #rhs); \
DOCTEST_WRAP_IN_TRY( \
_DOCTEST_RB.binary_assert<doctest::detail::binaryAssertComparison::comp>(lhs, \
rhs)) \
@@ -2459,7 +2624,7 @@
#define DOCTEST_UNARY_ASSERT(assert_type, expr) \
do { \
doctest::detail::ResultBuilder _DOCTEST_RB( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))); \
DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(DOCTEST_HANDLE_BRACED_VA_ARGS(expr))) \
DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \
@@ -2526,7 +2691,7 @@
do { \
int _DOCTEST_FAST_RES = doctest::detail::fast_binary_assert< \
doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr)); \
if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \
@@ -2538,8 +2703,7 @@
do { \
int _DOCTEST_FAST_RES = doctest::detail::fast_binary_assert< \
doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, \
- rhs); \
+ doctest::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, rhs); \
if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \
DOCTEST_BREAK_INTO_DEBUGGER(); \
doctest::detail::fastAssertThrowIfFlagSet(_DOCTEST_FAST_RES); \
@@ -2549,7 +2713,7 @@
#define DOCTEST_FAST_UNARY_ASSERT(assert_type, expr) \
do { \
int _DOCTEST_FAST_RES = doctest::detail::fast_unary_assert( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr)); \
if(_DOCTEST_FAST_RES & doctest::detail::assertAction::dbgbreak) \
@@ -2562,19 +2726,17 @@
#ifdef DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_FAST_BINARY_ASSERT(assert_type, expr, comparison) \
doctest::detail::fast_binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, \
+ doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr))
#else // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_FAST_BINARY_ASSERT(assert_type, lhs, rhs, comparison) \
doctest::detail::fast_binary_assert<doctest::detail::binaryAssertComparison::comparison>( \
- doctest::detail::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, \
- rhs)
+ doctest::assertType::assert_type, __FILE__, __LINE__, #lhs ", " #rhs, lhs, rhs)
#endif // DOCTEST_CONFIG_WITH_VARIADIC_MACROS
#define DOCTEST_FAST_UNARY_ASSERT(assert_type, expr) \
- doctest::detail::fast_unary_assert(doctest::detail::assertType::assert_type, __FILE__, \
- __LINE__, \
+ doctest::detail::fast_unary_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
DOCTEST_TOSTR(DOCTEST_HANDLE_BRACED_VA_ARGS(expr)), \
DOCTEST_HANDLE_BRACED_VA_ARGS(expr))
@@ -2779,6 +2941,8 @@
template <typename DOCTEST_UNUSED_TEMPLATE_TYPE> \
static inline doctest::String DOCTEST_ANONYMOUS(_DOCTEST_ANON_TRANSLATOR_)(signature)
+#define DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+
#define DOCTEST_INFO(x) ((void)0)
#define DOCTEST_CAPTURE(x) ((void)0)
#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0)
@@ -2980,6 +3144,7 @@
#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN
#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR
+#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER
#define INFO DOCTEST_INFO
#define CAPTURE DOCTEST_CAPTURE
#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT
@@ -3083,6 +3248,8 @@
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
+#if !defined(DOCTEST_CONFIG_DISABLE)
+
// this is here to clear the 'current test suite' for the current translation unit - at the top
DOCTEST_TEST_SUITE_END();
@@ -3112,6 +3279,8 @@
} // namespace detail
} // namespace doctest
+#endif // DOCTEST_CONFIG_DISABLE
+
DOCTEST_CLANG_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
DOCTEST_GCC_SUPPRESS_WARNING_POP
diff --git a/doctest/parts/doctest_impl.h b/doctest/parts/doctest_impl.h
index 20f58ad..5652a93 100644
--- a/doctest/parts/doctest_impl.h
+++ b/doctest/parts/doctest_impl.h
@@ -9,6 +9,7 @@
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wpadded")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wweak-vtables")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wglobal-constructors")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes")
@@ -65,40 +66,28 @@
DOCTEST_MSVC_SUPPRESS_WARNING(4640) // construction of local static object is not thread-safe
DOCTEST_MSVC_SUPPRESS_WARNING(5039) // pointer to potentially throwing function passed to extern C
DOCTEST_MSVC_SUPPRESS_WARNING(5045) // Spectre mitigation stuff
+DOCTEST_MSVC_SUPPRESS_WARNING(4626) // assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5027) // move assignment operator was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(5026) // move constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4625) // copy constructor was implicitly defined as deleted
+DOCTEST_MSVC_SUPPRESS_WARNING(4800) // forcing value to bool 'true' or 'false' (performance warning)
+// static analysis
+DOCTEST_MSVC_SUPPRESS_WARNING(26439) // This kind of function may not throw. Declare it 'noexcept'
+DOCTEST_MSVC_SUPPRESS_WARNING(26495) // Always initialize a member variable
+DOCTEST_MSVC_SUPPRESS_WARNING(26451) // Arithmetic overflow ...
+DOCTEST_MSVC_SUPPRESS_WARNING(26444) // Avoid unnamed objects with custom construction and dtr...
#if defined(DOCTEST_NO_CPP11_COMPAT)
DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
#endif // DOCTEST_NO_CPP11_COMPAT
-#define DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(f, arg) \
- do { \
- f(std::cout arg); \
- if(isDebuggerActive()) { \
- ContextState* p_cs = contextState; \
- bool with_col = p_cs->no_colors; \
- p_cs->no_colors = false; \
- std::ostringstream oss; \
- f(oss arg); \
- printToDebugConsole(oss.str().c_str()); \
- p_cs->no_colors = with_col; \
- } \
- } while(false)
-
-#define DOCTEST_LOG_START \
- do { \
- if(!contextState->hasLoggedCurrentTestStart) { \
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(logTestStart, \
- DOCTEST_COMMA * contextState->currentTest); \
- contextState->hasLoggedCurrentTestStart = true; \
- } \
- } while(false)
-
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
// required includes - will go only in one translation unit!
#include <ctime>
#include <cmath>
+#include <climits>
// borland (Embarcadero) compiler requires math.h and not cmath - https://github.com/onqtam/doctest/pull/37
#ifdef __BORLANDC__
#include <math.h>
@@ -115,6 +104,7 @@
#include <iomanip>
#include <vector>
#include <set>
+#include <map>
#include <exception>
#include <stdexcept>
#include <csignal>
@@ -199,83 +189,39 @@
#ifndef DOCTEST_CONFIG_DISABLE
- // this holds both parameters for the command line and runtime data for tests
- struct ContextState : TestAccessibleContextState //!OCLINT too many fields
+ // this holds both parameters from the command line and runtime data for tests
+ struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
{
- // == parameters from the command line
-
std::vector<std::vector<String> > filters;
- String order_by; // how tests should be ordered
- unsigned rand_seed; // the seed for rand ordering
+ std::vector<IReporter*> reporters_currently_used;
- unsigned first; // the first (matching) test to be executed
- unsigned last; // the last (matching) test to be executed
-
- int abort_after; // stop tests after this many failed assertions
- int subcase_filter_levels; // apply the subcase filters for the first N levels
- bool case_sensitive; // if filtering should be case sensitive
- bool exit; // if the program should be exited after the tests are ran/whatever
- bool duration; // print the time duration of each test case
- bool no_exitcode; // if the framework should return 0 as the exitcode
- bool no_run; // to not run the tests at all (can be done with an "*" exclude)
- bool no_version; // to not print the version of the framework
- bool no_colors; // if output to the console should be colorized
- bool force_colors; // forces the use of colors even when a tty cannot be detected
- bool no_breaks; // to not break into the debugger
- bool no_skip; // don't skip test cases which are marked to be skipped
- bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x):
- bool no_path_in_filenames; // if the path to files should be removed from the output
- bool no_line_numbers; // if source code line numbers should be omitted from the output
- bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!!
-
- bool help; // to print the help
- bool version; // to print the version
- bool count; // if only the count of matching tests is to be retreived
- bool list_test_cases; // to list all tests matching the filters
- bool list_test_suites; // to list all suites matching the filters
-
- // == data for the tests being ran
-
- unsigned numTestsPassingFilters;
- unsigned numTestSuitesPassingFilters;
- unsigned numFailed;
const TestCase* currentTest;
- bool hasLoggedCurrentTestStart;
- int numAssertionsForCurrentTestcase;
- int numAssertions;
- int numFailedAssertionsForCurrentTestcase;
- int numFailedAssertions;
- bool hasCurrentTestFailed;
std::vector<IContextScope*> contexts; // for logging with INFO() and friends
- std::vector<std::string> exceptionalContexts; // logging from INFO() due to an exception
+ std::vector<String> stringifiedContexts; // logging from INFO() due to an exception
// stuff for subcases
std::set<SubcaseSignature> subcasesPassed;
std::set<int> subcasesEnteredLevels;
- std::vector<Subcase> subcasesStack;
int subcasesCurrentLevel;
- bool subcasesHasSkipped;
void resetRunData() {
- numTestsPassingFilters = 0;
- numTestSuitesPassingFilters = 0;
- numFailed = 0;
- numAssertions = 0;
- numFailedAssertions = 0;
- numFailedAssertionsForCurrentTestcase = 0;
+ numTestCases = 0;
+ numTestCasesPassingFilters = 0;
+ numTestSuitesPassingFilters = 0;
+ numTestCasesFailed = 0;
+ numAsserts = 0;
+ numAssertsFailed = 0;
}
// cppcheck-suppress uninitMemberVar
ContextState()
- : filters(8) // 8 different filters total
- {
- resetRunData();
- }
+ : filters(9) // 9 different filters total
+ {}
};
- ContextState* contextState = 0;
+ ContextState* g_contextState = 0;
#endif // DOCTEST_CONFIG_DISABLE
} // namespace detail
@@ -385,6 +331,109 @@
std::ostream& operator<<(std::ostream& s, const String& in) { return s << in.c_str(); }
+namespace detail
+{
+ void color_to_stream(std::ostream&, Color::Enum) DOCTEST_BRANCH_ON_DISABLED({}, ;)
+} // namespace detail
+
+namespace Color
+{
+ std::ostream& operator<<(std::ostream& s, Color::Enum code) {
+ detail::color_to_stream(s, code);
+ return s;
+ }
+} // namespace Color
+
+const char* assertString(assertType::Enum at) {
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(
+ 4062) // enumerator 'x' in switch of enum 'y' is not handled
+ switch(at) { //!OCLINT missing default in switch statements
+ // clang-format off
+ case assertType::DT_WARN : return "WARN";
+ case assertType::DT_CHECK : return "CHECK";
+ case assertType::DT_REQUIRE : return "REQUIRE";
+
+ case assertType::DT_WARN_FALSE : return "WARN_FALSE";
+ case assertType::DT_CHECK_FALSE : return "CHECK_FALSE";
+ case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE";
+
+ case assertType::DT_WARN_THROWS : return "WARN_THROWS";
+ case assertType::DT_CHECK_THROWS : return "CHECK_THROWS";
+ case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS";
+
+ case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS";
+ case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS";
+ case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS";
+
+ case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW";
+ case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW";
+ case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW";
+
+ case assertType::DT_WARN_EQ : return "WARN_EQ";
+ case assertType::DT_CHECK_EQ : return "CHECK_EQ";
+ case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ";
+ case assertType::DT_WARN_NE : return "WARN_NE";
+ case assertType::DT_CHECK_NE : return "CHECK_NE";
+ case assertType::DT_REQUIRE_NE : return "REQUIRE_NE";
+ case assertType::DT_WARN_GT : return "WARN_GT";
+ case assertType::DT_CHECK_GT : return "CHECK_GT";
+ case assertType::DT_REQUIRE_GT : return "REQUIRE_GT";
+ case assertType::DT_WARN_LT : return "WARN_LT";
+ case assertType::DT_CHECK_LT : return "CHECK_LT";
+ case assertType::DT_REQUIRE_LT : return "REQUIRE_LT";
+ case assertType::DT_WARN_GE : return "WARN_GE";
+ case assertType::DT_CHECK_GE : return "CHECK_GE";
+ case assertType::DT_REQUIRE_GE : return "REQUIRE_GE";
+ case assertType::DT_WARN_LE : return "WARN_LE";
+ case assertType::DT_CHECK_LE : return "CHECK_LE";
+ case assertType::DT_REQUIRE_LE : return "REQUIRE_LE";
+
+ case assertType::DT_WARN_UNARY : return "WARN_UNARY";
+ case assertType::DT_CHECK_UNARY : return "CHECK_UNARY";
+ case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY";
+ case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE";
+ case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE";
+ case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE";
+
+ case assertType::DT_FAST_WARN_EQ : return "FAST_WARN_EQ";
+ case assertType::DT_FAST_CHECK_EQ : return "FAST_CHECK_EQ";
+ case assertType::DT_FAST_REQUIRE_EQ : return "FAST_REQUIRE_EQ";
+ case assertType::DT_FAST_WARN_NE : return "FAST_WARN_NE";
+ case assertType::DT_FAST_CHECK_NE : return "FAST_CHECK_NE";
+ case assertType::DT_FAST_REQUIRE_NE : return "FAST_REQUIRE_NE";
+ case assertType::DT_FAST_WARN_GT : return "FAST_WARN_GT";
+ case assertType::DT_FAST_CHECK_GT : return "FAST_CHECK_GT";
+ case assertType::DT_FAST_REQUIRE_GT : return "FAST_REQUIRE_GT";
+ case assertType::DT_FAST_WARN_LT : return "FAST_WARN_LT";
+ case assertType::DT_FAST_CHECK_LT : return "FAST_CHECK_LT";
+ case assertType::DT_FAST_REQUIRE_LT : return "FAST_REQUIRE_LT";
+ case assertType::DT_FAST_WARN_GE : return "FAST_WARN_GE";
+ case assertType::DT_FAST_CHECK_GE : return "FAST_CHECK_GE";
+ case assertType::DT_FAST_REQUIRE_GE : return "FAST_REQUIRE_GE";
+ case assertType::DT_FAST_WARN_LE : return "FAST_WARN_LE";
+ case assertType::DT_FAST_CHECK_LE : return "FAST_CHECK_LE";
+ case assertType::DT_FAST_REQUIRE_LE : return "FAST_REQUIRE_LE";
+
+ case assertType::DT_FAST_WARN_UNARY : return "FAST_WARN_UNARY";
+ case assertType::DT_FAST_CHECK_UNARY : return "FAST_CHECK_UNARY";
+ case assertType::DT_FAST_REQUIRE_UNARY : return "FAST_REQUIRE_UNARY";
+ case assertType::DT_FAST_WARN_UNARY_FALSE : return "FAST_WARN_UNARY_FALSE";
+ case assertType::DT_FAST_CHECK_UNARY_FALSE : return "FAST_CHECK_UNARY_FALSE";
+ case assertType::DT_FAST_REQUIRE_UNARY_FALSE: return "FAST_REQUIRE_UNARY_FALSE";
+ // clang-format on
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ return "";
+}
+
+bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
+ if(m_line != other.m_line)
+ return m_line < other.m_line;
+ if(std::strcmp(m_file, other.m_file) != 0)
+ return std::strcmp(m_file, other.m_file) < 0;
+ return std::strcmp(m_name, other.m_name) < 0;
+}
+
Approx::Approx(double value)
: m_epsilon(static_cast<double>(std::numeric_limits<float>::epsilon()) * 100)
, m_scale(1.0)
@@ -493,6 +542,14 @@
void Context::setOption(const char*, const char*) {}
bool Context::shouldExit() { return false; }
int Context::run() { return 0; }
+
+int IReporter::get_num_active_contexts() { return 0; }
+const IContextScope* const* IReporter::get_active_contexts() { return 0; }
+int IReporter::get_num_stringified_contexts() { return 0; }
+const String* IReporter::get_stringified_contexts() { return 0; }
+
+int registerReporter(const char*, int, IReporter*) { return 0; }
+
} // namespace doctest
#else // DOCTEST_CONFIG_DISABLE
@@ -565,21 +622,33 @@
{
namespace detail
{
+ typedef std::map<std::pair<int, String>, IReporter*> reporterMap;
+ reporterMap& getReporters() {
+ static reporterMap data;
+ return data;
+ }
+
+#define DOCTEST_ITERATE_THROUGH_REPORTERS(function, args) \
+ for(size_t iii = 0; iii < g_contextState->reporters_currently_used.size(); ++iii) \
+ g_contextState->reporters_currently_used[iii]->function(args)
+
TestCase::TestCase(funcType test, const char* file, unsigned line, const TestSuite& test_suite,
- const char* type, int template_id)
- : m_test(test)
- , m_name(0)
- , m_type(type)
- , m_test_suite(test_suite.m_test_suite)
- , m_description(test_suite.m_description)
- , m_skip(test_suite.m_skip)
- , m_may_fail(test_suite.m_may_fail)
- , m_should_fail(test_suite.m_should_fail)
- , m_expected_failures(test_suite.m_expected_failures)
- , m_timeout(test_suite.m_timeout)
- , m_file(file)
- , m_line(line)
- , m_template_id(template_id) {}
+ const char* type, int template_id) {
+ m_file = file;
+ m_line = line;
+ m_name = 0;
+ m_test_suite = test_suite.m_test_suite;
+ m_description = test_suite.m_description;
+ m_skip = test_suite.m_skip;
+ m_may_fail = test_suite.m_may_fail;
+ m_should_fail = test_suite.m_should_fail;
+ m_expected_failures = test_suite.m_expected_failures;
+ m_timeout = test_suite.m_timeout;
+
+ m_test = test;
+ m_type = type;
+ m_template_id = template_id;
+ }
TestCase& TestCase::operator*(const char* in) {
m_name = in;
@@ -593,10 +662,9 @@
}
TestCase& TestCase::operator=(const TestCase& other) {
- m_test = other.m_test;
- m_full_name = other.m_full_name;
+ m_file = other.m_file;
+ m_line = other.m_line;
m_name = other.m_name;
- m_type = other.m_type;
m_test_suite = other.m_test_suite;
m_description = other.m_description;
m_skip = other.m_skip;
@@ -604,9 +672,11 @@
m_should_fail = other.m_should_fail;
m_expected_failures = other.m_expected_failures;
m_timeout = other.m_timeout;
- m_file = other.m_file;
- m_line = other.m_line;
- m_template_id = other.m_template_id;
+
+ m_test = other.m_test;
+ m_type = other.m_type;
+ m_template_id = other.m_template_id;
+ m_full_name = other.m_full_name;
if(m_template_id != -1)
m_name = m_full_name.c_str();
@@ -622,95 +692,13 @@
return m_template_id < other.m_template_id;
}
- const char* assertString(assertType::Enum at) {
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(
- 4062) // enumerator 'x' in switch of enum 'y' is not handled
- switch(at) { //!OCLINT missing default in switch statements
- // clang-format off
- case assertType::DT_WARN : return "WARN";
- case assertType::DT_CHECK : return "CHECK";
- case assertType::DT_REQUIRE : return "REQUIRE";
-
- case assertType::DT_WARN_FALSE : return "WARN_FALSE";
- case assertType::DT_CHECK_FALSE : return "CHECK_FALSE";
- case assertType::DT_REQUIRE_FALSE : return "REQUIRE_FALSE";
-
- case assertType::DT_WARN_THROWS : return "WARN_THROWS";
- case assertType::DT_CHECK_THROWS : return "CHECK_THROWS";
- case assertType::DT_REQUIRE_THROWS : return "REQUIRE_THROWS";
-
- case assertType::DT_WARN_THROWS_AS : return "WARN_THROWS_AS";
- case assertType::DT_CHECK_THROWS_AS : return "CHECK_THROWS_AS";
- case assertType::DT_REQUIRE_THROWS_AS : return "REQUIRE_THROWS_AS";
-
- case assertType::DT_WARN_NOTHROW : return "WARN_NOTHROW";
- case assertType::DT_CHECK_NOTHROW : return "CHECK_NOTHROW";
- case assertType::DT_REQUIRE_NOTHROW : return "REQUIRE_NOTHROW";
-
- case assertType::DT_WARN_EQ : return "WARN_EQ";
- case assertType::DT_CHECK_EQ : return "CHECK_EQ";
- case assertType::DT_REQUIRE_EQ : return "REQUIRE_EQ";
- case assertType::DT_WARN_NE : return "WARN_NE";
- case assertType::DT_CHECK_NE : return "CHECK_NE";
- case assertType::DT_REQUIRE_NE : return "REQUIRE_NE";
- case assertType::DT_WARN_GT : return "WARN_GT";
- case assertType::DT_CHECK_GT : return "CHECK_GT";
- case assertType::DT_REQUIRE_GT : return "REQUIRE_GT";
- case assertType::DT_WARN_LT : return "WARN_LT";
- case assertType::DT_CHECK_LT : return "CHECK_LT";
- case assertType::DT_REQUIRE_LT : return "REQUIRE_LT";
- case assertType::DT_WARN_GE : return "WARN_GE";
- case assertType::DT_CHECK_GE : return "CHECK_GE";
- case assertType::DT_REQUIRE_GE : return "REQUIRE_GE";
- case assertType::DT_WARN_LE : return "WARN_LE";
- case assertType::DT_CHECK_LE : return "CHECK_LE";
- case assertType::DT_REQUIRE_LE : return "REQUIRE_LE";
-
- case assertType::DT_WARN_UNARY : return "WARN_UNARY";
- case assertType::DT_CHECK_UNARY : return "CHECK_UNARY";
- case assertType::DT_REQUIRE_UNARY : return "REQUIRE_UNARY";
- case assertType::DT_WARN_UNARY_FALSE : return "WARN_UNARY_FALSE";
- case assertType::DT_CHECK_UNARY_FALSE : return "CHECK_UNARY_FALSE";
- case assertType::DT_REQUIRE_UNARY_FALSE : return "REQUIRE_UNARY_FALSE";
-
- case assertType::DT_FAST_WARN_EQ : return "FAST_WARN_EQ";
- case assertType::DT_FAST_CHECK_EQ : return "FAST_CHECK_EQ";
- case assertType::DT_FAST_REQUIRE_EQ : return "FAST_REQUIRE_EQ";
- case assertType::DT_FAST_WARN_NE : return "FAST_WARN_NE";
- case assertType::DT_FAST_CHECK_NE : return "FAST_CHECK_NE";
- case assertType::DT_FAST_REQUIRE_NE : return "FAST_REQUIRE_NE";
- case assertType::DT_FAST_WARN_GT : return "FAST_WARN_GT";
- case assertType::DT_FAST_CHECK_GT : return "FAST_CHECK_GT";
- case assertType::DT_FAST_REQUIRE_GT : return "FAST_REQUIRE_GT";
- case assertType::DT_FAST_WARN_LT : return "FAST_WARN_LT";
- case assertType::DT_FAST_CHECK_LT : return "FAST_CHECK_LT";
- case assertType::DT_FAST_REQUIRE_LT : return "FAST_REQUIRE_LT";
- case assertType::DT_FAST_WARN_GE : return "FAST_WARN_GE";
- case assertType::DT_FAST_CHECK_GE : return "FAST_CHECK_GE";
- case assertType::DT_FAST_REQUIRE_GE : return "FAST_REQUIRE_GE";
- case assertType::DT_FAST_WARN_LE : return "FAST_WARN_LE";
- case assertType::DT_FAST_CHECK_LE : return "FAST_CHECK_LE";
- case assertType::DT_FAST_REQUIRE_LE : return "FAST_REQUIRE_LE";
-
- case assertType::DT_FAST_WARN_UNARY : return "FAST_WARN_UNARY";
- case assertType::DT_FAST_CHECK_UNARY : return "FAST_CHECK_UNARY";
- case assertType::DT_FAST_REQUIRE_UNARY : return "FAST_REQUIRE_UNARY";
- case assertType::DT_FAST_WARN_UNARY_FALSE : return "FAST_WARN_UNARY_FALSE";
- case assertType::DT_FAST_CHECK_UNARY_FALSE : return "FAST_CHECK_UNARY_FALSE";
- case assertType::DT_FAST_REQUIRE_UNARY_FALSE: return "FAST_REQUIRE_UNARY_FALSE";
- // clang-format on
- }
- DOCTEST_MSVC_SUPPRESS_WARNING_POP
- return "";
- }
-
bool checkIfShouldThrow(assertType::Enum at) {
if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
return true;
if((at & assertType::is_check) //!OCLINT bitwise operator in conditional
- && contextState->abort_after > 0 &&
- contextState->numFailedAssertions >= contextState->abort_after)
+ && g_contextState->abort_after > 0 &&
+ g_contextState->numAssertsFailed >= g_contextState->abort_after)
return true;
return false;
@@ -773,7 +761,7 @@
//}
// checks if the name matches any of the filters (and can be configured what to do when empty)
- bool matchesAny(const char* name, const std::vector<String>& filters, int matchEmpty,
+ bool matchesAny(const char* name, const std::vector<String>& filters, bool matchEmpty,
bool caseSensitive) {
if(filters.empty() && matchEmpty)
return true;
@@ -828,23 +816,14 @@
UInt64 m_ticks;
};
- TestAccessibleContextState* getTestsContextState() { return contextState; }
+ Timer g_timer;
- // TODO: remove this from here
- void logTestEnd();
-
- bool SubcaseSignature::operator<(const SubcaseSignature& other) const {
- if(m_line != other.m_line)
- return m_line < other.m_line;
- if(std::strcmp(m_file, other.m_file) != 0)
- return std::strcmp(m_file, other.m_file) < 0;
- return std::strcmp(m_name, other.m_name) < 0;
- }
+ const ContextOptions* getContextOptions() { return g_contextState; }
Subcase::Subcase(const char* name, const char* file, int line)
: m_signature(name, file, line)
, m_entered(false) {
- ContextState* s = contextState;
+ ContextState* s = g_contextState;
// if we have already completed it
if(s->subcasesPassed.count(m_signature) != 0)
@@ -852,25 +831,22 @@
// check subcase filters
if(s->subcasesCurrentLevel < s->subcase_filter_levels) {
- if(!matchesAny(m_signature.m_name, s->filters[6], 1, s->case_sensitive))
+ if(!matchesAny(m_signature.m_name, s->filters[6], true, s->case_sensitive))
return;
- if(matchesAny(m_signature.m_name, s->filters[7], 0, s->case_sensitive))
+ if(matchesAny(m_signature.m_name, s->filters[7], false, s->case_sensitive))
return;
}
// if a Subcase on the same level has already been entered
if(s->subcasesEnteredLevels.count(s->subcasesCurrentLevel) != 0) {
- s->subcasesHasSkipped = true;
+ s->should_reenter = true;
return;
}
- s->subcasesStack.push_back(*this);
- if(s->hasLoggedCurrentTestStart)
- logTestEnd();
- s->hasLoggedCurrentTestStart = false;
-
s->subcasesEnteredLevels.insert(s->subcasesCurrentLevel++);
m_entered = true;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_start, m_signature);
}
Subcase::Subcase(const Subcase& other)
@@ -880,18 +856,14 @@
Subcase::~Subcase() {
if(m_entered) {
- ContextState* s = contextState;
+ ContextState* s = g_contextState;
s->subcasesCurrentLevel--;
// only mark the subcase as passed if no subcases have been skipped
- if(s->subcasesHasSkipped == false)
+ if(s->should_reenter == false)
s->subcasesPassed.insert(m_signature);
- if(!s->subcasesStack.empty())
- s->subcasesStack.pop_back();
- if(s->hasLoggedCurrentTestStart)
- logTestEnd();
- s->hasLoggedCurrentTestStart = false;
+ DOCTEST_ITERATE_THROUGH_REPORTERS(subcase_end, m_signature);
}
}
@@ -960,65 +932,41 @@
return 0;
}
- namespace Color
- {
- enum Code
- {
- None = 0,
- White,
- Red,
- Green,
- Blue,
- Cyan,
- Yellow,
- Grey,
-
- Bright = 0x10,
-
- BrightRed = Bright | Red,
- BrightGreen = Bright | Green,
- LightGrey = Bright | Grey,
- BrightWhite = Bright | White
- };
-
#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- HANDLE g_stdoutHandle;
- WORD g_originalForegroundAttributes;
- WORD g_originalBackgroundAttributes;
- bool g_attrsInitted = false;
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ HANDLE g_stdoutHandle;
+ WORD g_origFgAttrs;
+ WORD g_origBgAttrs;
+ bool g_attrsInitted = false;
- void init() {
-#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- if(!g_attrsInitted) {
- g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
- g_attrsInitted = true;
- CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
- GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo);
- g_originalForegroundAttributes =
- csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
- BACKGROUND_BLUE | BACKGROUND_INTENSITY);
- g_originalBackgroundAttributes =
- csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
- FOREGROUND_BLUE | FOREGROUND_INTENSITY);
- }
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ int colors_init() {
+ if(!g_attrsInitted) {
+ g_stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+ g_attrsInitted = true;
+ CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
+ GetConsoleScreenBufferInfo(g_stdoutHandle, &csbiInfo);
+ g_origFgAttrs = csbiInfo.wAttributes & ~(BACKGROUND_GREEN | BACKGROUND_RED |
+ BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+ g_origBgAttrs = csbiInfo.wAttributes & ~(FOREGROUND_GREEN | FOREGROUND_RED |
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY);
}
+ return 0;
+ }
- std::ostream& operator<<(std::ostream& s, Color::Code
-#ifndef DOCTEST_CONFIG_COLORS_NONE
- code
-#endif // DOCTEST_CONFIG_COLORS_NONE
- ) {
- const ContextState* p = contextState;
- if(p->no_colors)
- return s;
+ int dumy_init_console_colors = colors_init();
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+
+ void color_to_stream(std::ostream& s, Color::Enum code) {
+ ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS
+ ((void)code); // for DOCTEST_CONFIG_COLORS_NONE
+ const ContextState* p = g_contextState;
+ if(p->no_colors)
+ return;
#ifdef DOCTEST_CONFIG_COLORS_ANSI
- if(isatty(STDOUT_FILENO) == false && p->force_colors == false)
- return s;
+ if(isatty(STDOUT_FILENO) == false && p->force_colors == false)
+ return;
- const char* col = "";
- // clang-format off
+ const char* col = "";
+ // clang-format off
switch(code) { //!OCLINT missing break in switch statement / unnecessary default statement in covered switch statement
case Color::Red: col = "[0;31m"; break;
case Color::Green: col = "[0;32m"; break;
@@ -1035,40 +983,36 @@
case Color::White:
default: col = "[0m";
}
- // clang-format on
- s << "\033" << col;
+ // clang-format on
+ s << "\033" << col;
#endif // DOCTEST_CONFIG_COLORS_ANSI
#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
- if(isatty(fileno(stdout)) == false && p->force_colors == false)
- return s;
+ if(isatty(fileno(stdout)) == false && p->force_colors == false)
+ return;
-#define DOCTEST_SET_ATTR(x) \
- SetConsoleTextAttribute(g_stdoutHandle, x | g_originalBackgroundAttributes)
+#define DOCTEST_SET_ATTR(x) SetConsoleTextAttribute(g_stdoutHandle, x | g_origBgAttrs)
- // clang-format off
- switch (code) {
- case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
- case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break;
- case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break;
- case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break;
- case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break;
- case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break;
- case Color::Grey: DOCTEST_SET_ATTR(0); break;
- case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break;
- case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break;
- case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;
- case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
- case Color::None:
- case Color::Bright: // invalid
- default: DOCTEST_SET_ATTR(g_originalForegroundAttributes);
- }
- // clang-format on
-#undef DOCTEST_SET_ATTR
-#endif // DOCTEST_CONFIG_COLORS_WINDOWS
- return s;
+ // clang-format off
+ switch (code) {
+ case Color::White: DOCTEST_SET_ATTR(FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::Red: DOCTEST_SET_ATTR(FOREGROUND_RED); break;
+ case Color::Green: DOCTEST_SET_ATTR(FOREGROUND_GREEN); break;
+ case Color::Blue: DOCTEST_SET_ATTR(FOREGROUND_BLUE); break;
+ case Color::Cyan: DOCTEST_SET_ATTR(FOREGROUND_BLUE | FOREGROUND_GREEN); break;
+ case Color::Yellow: DOCTEST_SET_ATTR(FOREGROUND_RED | FOREGROUND_GREEN); break;
+ case Color::Grey: DOCTEST_SET_ATTR(0); break;
+ case Color::LightGrey: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY); break;
+ case Color::BrightRed: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_RED); break;
+ case Color::BrightGreen: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN); break;
+ case Color::BrightWhite: DOCTEST_SET_ATTR(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE); break;
+ case Color::None:
+ case Color::Bright: // invalid
+ default: DOCTEST_SET_ATTR(g_origFgAttrs);
}
- } // namespace Color
+ // clang-format on
+#endif // DOCTEST_CONFIG_COLORS_WINDOWS
+ }
std::vector<const IExceptionTranslator*>& getExceptionTranslators() {
static std::vector<const IExceptionTranslator*> data;
@@ -1132,22 +1076,20 @@
void toStream(std::ostream* s, int long long unsigned in) { *s << in; }
#endif // DOCTEST_CONFIG_WITH_LONG_LONG
- void addToContexts(IContextScope* ptr) { contextState->contexts.push_back(ptr); }
- void popFromContexts() { contextState->contexts.pop_back(); }
+ void addToContexts(IContextScope* ptr) { g_contextState->contexts.push_back(ptr); }
+ void popFromContexts() { g_contextState->contexts.pop_back(); }
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4996) // std::uncaught_exception is deprecated in C++17
DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations")
- void useContextIfExceptionOccurred(IContextScope* ptr) {
+ void stringifyContextIfExceptionOccurred(IContextScope* ptr) {
if(std::uncaught_exception()) {
std::ostringstream s;
- ptr->build(&s);
- contextState->exceptionalContexts.push_back(s.str());
+ ptr->stringify(&s);
+ g_contextState->stringifiedContexts.push_back(s.str().c_str());
}
}
DOCTEST_GCC_SUPPRESS_WARNING_POP
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- void printSummary(std::ostream& s);
-
#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
void reportFatal(const std::string&) {}
struct FatalConditionHandler
@@ -1295,53 +1237,6 @@
#endif // DOCTEST_PLATFORM_WINDOWS
#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
- void separator_to_stream(std::ostream& s) {
- s << Color::Yellow
- << "===============================================================================\n";
- }
-
- // depending on the current options this will remove the path of filenames
- const char* fileForOutput(const char* file) {
- if(contextState->no_path_in_filenames) {
- const char* back = std::strrchr(file, '\\');
- const char* forward = std::strrchr(file, '/');
- if(back || forward) {
- if(back > forward)
- forward = back;
- return forward + 1;
- }
- }
- return file;
- }
-
- void file_line_to_stream(std::ostream& s, const char* file, int line, const char* tail = "") {
- s << Color::LightGrey << fileForOutput(file) << (contextState->gnu_file_line ? ":" : "(")
- << (contextState->no_line_numbers ? 0 : line) // 0 or the real num depending on the option
- << (contextState->gnu_file_line ? ":" : "):") << tail;
- }
-
- const char* getSuccessOrFailString(bool success, assertType::Enum at, const char* success_str) {
- if(success)
- return success_str;
- if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
- return "WARNING: ";
- if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
- return "ERROR: ";
- if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
- return "FATAL ERROR: ";
- return "";
- }
-
- Color::Code getSuccessOrFailColor(bool success, assertType::Enum at) {
- return success ? Color::BrightGreen :
- (at & assertType::is_warn) ? Color::Yellow : Color::Red;
- }
-
- void successOrFailColoredStringToStream(std::ostream& s, bool success, assertType::Enum at,
- const char* success_str = "SUCCESS: ") {
- s << getSuccessOrFailColor(success, at) << getSuccessOrFailString(success, at, success_str);
- }
-
#ifdef DOCTEST_PLATFORM_MAC
#include <sys/types.h>
#include <unistd.h>
@@ -1380,134 +1275,54 @@
#endif // Platform
#ifdef DOCTEST_PLATFORM_WINDOWS
- void myOutputDebugString(const String& text) { ::OutputDebugStringA(text.c_str()); }
+ void myOutputDebugString(const char* text) { ::OutputDebugStringA(text); }
#else
// TODO: integration with XCode and other IDEs
- void myOutputDebugString(const String&) {}
+ void myOutputDebugString(const char*) {}
#endif // Platform
- void printToDebugConsole(const String& text) {
- if(isDebuggerActive())
- myOutputDebugString(text);
+ void addAssert(assertType::Enum at) {
+ if((at & assertType::is_warn) == 0) { //!OCLINT bitwise operator in conditional
+ g_contextState->numAsserts++;
+ g_contextState->numAssertsForCurrentTestCase++;
+ }
}
void addFailedAssert(assertType::Enum at) {
if((at & assertType::is_warn) == 0) { //!OCLINT bitwise operator in conditional
- contextState->numFailedAssertions++;
- contextState->numFailedAssertionsForCurrentTestcase++;
- contextState->hasCurrentTestFailed = true;
+ g_contextState->numAssertsFailed++;
+ g_contextState->numAssertsFailedForCurrentTestCase++;
}
}
- std::ostream& operator<<(std::ostream& s, const std::vector<IContextScope*>& contexts) {
- if(!contexts.empty())
- s << Color::None << " logged: ";
- for(size_t i = 0; i < contexts.size(); ++i) {
- s << (i == 0 ? "" : " ");
- contexts[i]->build(&s);
- s << "\n";
- }
- s << "\n";
- return s;
- }
-
- void logTestStart(std::ostream& s, const TestCase& tc) {
- separator_to_stream(s);
- 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')
- s << Color::Yellow << "TEST SUITE: " << Color::None << tc.m_test_suite << "\n";
- if(strncmp(tc.m_name, " Scenario:", 11) != 0)
- s << Color::None << "TEST CASE: ";
- s << Color::None << tc.m_name << "\n";
-
- std::vector<Subcase>& subcasesStack = contextState->subcasesStack;
- for(unsigned i = 0; i < subcasesStack.size(); ++i)
- if(subcasesStack[i].m_signature.m_name[0] != '\0')
- s << " " << subcasesStack[i].m_signature.m_name << "\n";
-
- s << "\n";
- }
-
- void logTestEnd() {}
-
- void logTestException(std::ostream& s, const TestCase& tc, const String& str, bool crash) {
- file_line_to_stream(s, tc.m_file, tc.m_line, " ");
- successOrFailColoredStringToStream(s, false,
- crash ? assertType::is_require : assertType::is_check);
- s << Color::Red << (crash ? "test case CRASHED: " : "test case THREW exception: ")
- << Color::Cyan << str << "\n";
-
- if(!contextState->exceptionalContexts.empty()) {
- s << Color::None << " logged: ";
- for(size_t i = contextState->exceptionalContexts.size(); i > 0; --i)
- s << (i == contextState->exceptionalContexts.size() ? "" : " ")
- << contextState->exceptionalContexts[i - 1] << "\n";
- }
- s << "\n";
- }
-
#if defined(DOCTEST_CONFIG_POSIX_SIGNALS) || defined(DOCTEST_CONFIG_WINDOWS_SEH)
void reportFatal(const std::string& message) {
- DOCTEST_LOG_START;
+ g_contextState->seconds_so_far += g_timer.getElapsedSeconds();
+ g_contextState->failure_flags |= TestCaseFailureReason::Crash;
+ g_contextState->error_string = message.c_str();
+ g_contextState->should_reenter = false;
- contextState->numAssertions += contextState->numAssertionsForCurrentTestcase;
+ // TODO: end all currently opened subcases...?
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(
- logTestException,
- DOCTEST_COMMA * contextState->currentTest DOCTEST_COMMA message.c_str()
- DOCTEST_COMMA true);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_contextState);
- logTestEnd();
- contextState->numFailed++;
+ g_contextState->numTestCasesFailed++;
- printSummary(std::cout);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_contextState);
}
#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
- void logAssert(std::ostream& s, const ResultBuilder& rb) {
- file_line_to_stream(s, rb.m_file, rb.m_line, " ");
- successOrFailColoredStringToStream(s, !rb.m_failed, rb.m_at);
- if((rb.m_at & assertType::is_throws_as) == 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_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_result.m_passed ? "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_result.m_decomposition
- << " )\n";
- }
-
- s << contextState->contexts;
- }
-
ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
- const char* exception_type)
- : m_test_case(*contextState->currentTest)
- , m_at(at)
- , m_file(file)
- , m_line(line)
- , m_expr(expr)
- , m_failed(false)
- , m_threw(false)
- , m_threw_as(false)
- , m_exception_type(exception_type) {
+ const char* exception_type) {
+ m_test_case = g_contextState->currentTest;
+ m_at = at;
+ m_file = file;
+ m_line = line;
+ m_expr = expr;
+ m_failed = true;
+ m_threw = false;
+ m_threw_as = false;
+ m_exception_type = exception_type;
#if DOCTEST_MSVC
if(m_expr[0] == ' ') // this happens when variadic macros are disabled under MSVC
++m_expr;
@@ -1523,30 +1338,22 @@
}
bool ResultBuilder::log() {
- if((m_at & assertType::is_warn) == 0) //!OCLINT bitwise operator in conditional
- contextState->numAssertionsForCurrentTestcase++;
+ addAssert(m_at);
if(m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional
m_failed = !m_threw;
- } else if(m_at & //!OCLINT bitwise operator in conditional
- assertType::is_throws_as) {
+ } else if(m_at & assertType::is_throws_as) { //!OCLINT bitwise operator in conditional
m_failed = !m_threw_as;
- } else if(m_at & //!OCLINT bitwise operator in conditional
- assertType::is_nothrow) {
+ } else if(m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional
m_failed = m_threw;
- } else {
- m_failed = m_result;
}
- if(m_failed || contextState->success) {
- DOCTEST_LOG_START;
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(logAssert, DOCTEST_COMMA * this);
- }
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_assert, *this);
if(m_failed)
addFailedAssert(m_at);
- return m_failed && isDebuggerActive() && !contextState->no_breaks; // break into debugger
+ return m_failed && isDebuggerActive() && !g_contextState->no_breaks; // break into debugger
}
void ResultBuilder::react() const {
@@ -1554,33 +1361,26 @@
throwException();
}
- MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity)
- : m_stream(createStream())
- , m_file(file)
- , m_line(line)
- , m_severity(severity) {}
-
- void MessageBuilder::log(std::ostream& s) {
- file_line_to_stream(s, m_file, m_line, " ");
- s << getSuccessOrFailColor(false, m_severity)
- << getSuccessOrFailString(m_severity & assertType::is_warn, m_severity, "MESSAGE: ");
- s << Color::None << getStreamResult(m_stream) << "\n";
- s << contextState->contexts;
+ MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) {
+ m_stream = createStream();
+ m_file = file;
+ m_line = line;
+ m_severity = severity;
}
bool MessageBuilder::log() {
- DOCTEST_LOG_START;
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(log, DOCTEST_EMPTY);
+ m_string = getStreamResult(m_stream);
+ DOCTEST_ITERATE_THROUGH_REPORTERS(log_message, *this);
const bool isWarn = m_severity & assertType::is_warn;
// warn is just a message in this context so we dont treat it as an assert
if(!isWarn) {
- contextState->numAssertionsForCurrentTestcase++;
+ addAssert(m_severity);
addFailedAssert(m_severity);
}
- return isDebuggerActive() && !contextState->no_breaks && !isWarn; // break into debugger
+ return isDebuggerActive() && !g_contextState->no_breaks && !isWarn; // break into debugger
}
void MessageBuilder::react() {
@@ -1590,6 +1390,428 @@
MessageBuilder::~MessageBuilder() { freeStream(m_stream); }
+ struct ConsoleReporter : public IReporter
+ {
+ std::ostream& s;
+ bool hasLoggedCurrentTestStart;
+ std::vector<SubcaseSignature> subcasesStack;
+
+ // caching pointers to objects of these types - safe to do
+ const ContextOptions* opt;
+ const TestCaseData* tc;
+
+ ConsoleReporter(std::ostream& in)
+ : s(in) {}
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE HELPERS USED BY THE OVERRIDES OF THE VIRTUAL METHODS OF THE INTERFACE
+ // =========================================================================================
+
+ void separator_to_stream() {
+ s << Color::Yellow
+ << "==============================================================================="
+ "\n";
+ }
+
+ // depending on the current options this will remove the path of filenames
+ const char* file_for_output(const char* file) {
+ if(opt->no_path_in_filenames) {
+ const char* back = std::strrchr(file, '\\');
+ const char* forward = std::strrchr(file, '/');
+ if(back || forward) {
+ if(back > forward)
+ forward = back;
+ return forward + 1;
+ }
+ }
+ return file;
+ }
+
+ void file_line_to_stream(const char* file, int line, const char* tail = "") {
+ s << Color::LightGrey << file_for_output(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)
+ return success_str;
+ if(at & assertType::is_warn) //!OCLINT bitwise operator in conditional
+ return "WARNING: ";
+ if(at & assertType::is_check) //!OCLINT bitwise operator in conditional
+ return "ERROR: ";
+ if(at & assertType::is_require) //!OCLINT bitwise operator in conditional
+ return "FATAL ERROR: ";
+ return "";
+ }
+
+ Color::Enum getSuccessOrFailColor(bool success, assertType::Enum at) {
+ return success ? Color::BrightGreen :
+ (at & assertType::is_warn) ? Color::Yellow : Color::Red;
+ }
+
+ void successOrFailColoredStringToStream(bool success, assertType::Enum at,
+ const char* success_str = "SUCCESS: ") {
+ s << getSuccessOrFailColor(success, at)
+ << getSuccessOrFailString(success, at, success_str);
+ }
+
+ void log_contexts() {
+ int num_contexts = get_num_active_contexts();
+ if(num_contexts) {
+ const IContextScope* const* contexts = get_active_contexts();
+
+ s << Color::None << " logged: ";
+ for(int i = 0; i < num_contexts; ++i) {
+ s << (i == 0 ? "" : " ");
+ contexts[i]->stringify(&s);
+ s << "\n";
+ }
+ }
+
+ s << "\n";
+ }
+
+ void logTestStart() {
+ if(hasLoggedCurrentTestStart)
+ return;
+
+ separator_to_stream();
+ file_line_to_stream(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')
+ s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n";
+ if(strncmp(tc->m_name, " Scenario:", 11) != 0)
+ s << Color::None << "TEST CASE: ";
+ s << Color::None << tc->m_name << "\n";
+
+ for(unsigned i = 0; i < subcasesStack.size(); ++i)
+ if(subcasesStack[i].m_name[0] != '\0')
+ s << " " << subcasesStack[i].m_name << "\n";
+
+ s << "\n";
+
+ hasLoggedCurrentTestStart = true;
+ }
+
+ // =========================================================================================
+ // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE
+ // =========================================================================================
+
+ void test_run_start(const ContextOptions& o) DOCTEST_OVERRIDE { opt = &o; }
+
+ void test_run_end(const TestRunStats& p) DOCTEST_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) DOCTEST_OVERRIDE {
+ hasLoggedCurrentTestStart = false;
+ tc = ∈
+ }
+
+ void test_case_end(const CurrentTestCaseStats& st) DOCTEST_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) {
+ const String* 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) DOCTEST_OVERRIDE {
+ subcasesStack.push_back(subc);
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void subcase_end(const SubcaseSignature& /*subc*/) DOCTEST_OVERRIDE {
+ subcasesStack.pop_back();
+ hasLoggedCurrentTestStart = false;
+ }
+
+ void log_assert(const AssertData& rb) DOCTEST_OVERRIDE {
+ if(!rb.m_failed && !opt->success)
+ return;
+
+ 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) == 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_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_decomposition
+ << " )\n";
+ }
+
+ log_contexts();
+ }
+
+ void log_message(const MessageData& mb) DOCTEST_OVERRIDE {
+ 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&) DOCTEST_OVERRIDE {}
+ };
+
+ // 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(g_contextState->no_version == false)
+ s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
+ << DOCTEST_VERSION_STR << "\"\n";
+ }
+
+ void printIntro() {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None << "run with \"--help\" for options\n";
+ }
+
+ void printHelp() {
+ printVersion();
+ // clang-format off
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filter values: \"str1,str2,str3\" (comma separated strings)\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "filters use wildcards for matching strings\n";
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "something passes a filter if any of the strings in a filter matches\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"dt-\" PREFIX!!!\n";
+ s << Color::Cyan << "[doctest]\n" << Color::None;
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "Query flags - the program quits after them. Available:\n\n";
+ s << " -?, --help, -h prints this message\n";
+ s << " -v, --version prints the version\n";
+ s << " -c, --count prints the number of matching tests\n";
+ s << " -ltc, --list-test-cases lists all matching tests by name\n";
+ s << " -lts, --list-test-suites lists all matching test suites\n";
+ s << " -lr, --list-reporters lists all registered reporters\n\n";
+ // ================================================================================== << 79
+ s << Color::Cyan << "[doctest] " << Color::None;
+ s << "The available <int>/<string> options/filters are:\n\n";
+ s << " -tc, --test-case=<filters> filters tests by their name\n";
+ s << " -tce, --test-case-exclude=<filters> filters OUT tests by their name\n";
+ s << " -sf, --source-file=<filters> filters tests by their file\n";
+ s << " -sfe, --source-file-exclude=<filters> filters OUT tests by their file\n";
+ s << " -ts, --test-suite=<filters> filters tests by their test suite\n";
+ s << " -tse, --test-suite-exclude=<filters> filters OUT tests by their test suite\n";
+ s << " -sc, --subcase=<filters> filters subcases by their name\n";
+ s << " -sce, --subcase-exclude=<filters> filters OUT subcases by their name\n";
+ s << " -r, --reporters=<filters> reporters to use (console is default)\n";
+ s << " -ob, --order-by=<string> how the tests should be ordered\n";
+ s << " <string> - by [file/suite/name/rand]\n";
+ s << " -rs, --rand-seed=<int> seed for random ordering\n";
+ s << " -f, --first=<int> the first test passing the filters to\n";
+ s << " execute - for range-based execution\n";
+ s << " -l, --last=<int> the last test passing the filters to\n";
+ s << " execute - for range-based execution\n";
+ s << " -aa, --abort-after=<int> stop after <int> failed assertions\n";
+ s << " -scfl,--subcase-filter-levels=<int> apply filters for the first <int> levels\n";
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
+ s << " -s, --success=<bool> include successful assertions in output\n";
+ s << " -cs, --case-sensitive=<bool> filters being treated as case sensitive\n";
+ s << " -e, --exit=<bool> exits after the tests finish\n";
+ s << " -d, --duration=<bool> prints the time duration of each test\n";
+ s << " -nt, --no-throw=<bool> skips exceptions-related assert checks\n";
+ s << " -ne, --no-exitcode=<bool> returns (or exits) always with success\n";
+ s << " -nr, --no-run=<bool> skips all runtime doctest operations\n";
+ s << " -nv, --no-version=<bool> omit the framework version in the output\n";
+ s << " -nc, --no-colors=<bool> disables colors in output\n";
+ s << " -fc, --force-colors=<bool> use colors even when not in a tty\n";
+ s << " -nb, --no-breaks=<bool> disables breakpoints in debuggers\n";
+ s << " -ns, --no-skip=<bool> don't skip test cases marked as skip\n";
+ s << " -gfl, --gnu-file-line=<bool> :n: vs (n): for line numbers in output\n";
+ s << " -npf, --no-path-filenames=<bool> only filenames and no paths in output\n";
+ s << " -nln, --no-line-numbers=<bool> 0 instead of real line numbers in output\n";
+ // ================================================================================== << 79
+ // clang-format on
+
+ s << Color::Cyan << "\n[doctest] " << Color::None;
+ s << "for more information visit the project documentation\n\n";
+ }
+
+ void printRegisteredReporters() {
+ printVersion();
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all registered reporters\n";
+ for(reporterMap::iterator it = getReporters().begin(); it != getReporters().end(); ++it)
+ s << "priority: " << std::setw(5) << it->first.first
+ << " name: " << it->first.second << "\n";
+ }
+
+ void output_query_results() {
+ separator_to_stream();
+ if(g_contextState->count || g_contextState->list_test_cases) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_contextState->numTestCasesPassingFilters << "\n";
+ } else if(g_contextState->list_test_suites) {
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "unskipped test cases passing the current filters: "
+ << g_contextState->numTestCasesPassingFilters << "\n";
+ s << Color::Cyan << "[doctest] " << Color::None
+ << "test suites with unskipped test cases passing the current filters: "
+ << g_contextState->numTestSuitesPassingFilters << "\n";
+ }
+ }
+
+ void output_query_preamble_test_cases() {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n";
+ separator_to_stream();
+ }
+
+ void output_query_preamble_test_suites() {
+ s << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
+ separator_to_stream();
+ }
+
+ void output_c_string_with_newline(const char* str) { s << Color::None << str << "\n"; }
+ };
+
+ struct DebugOutputWindowReporter : public ConsoleReporter
+ {
+ std::ostringstream oss;
+
+ DebugOutputWindowReporter()
+ : ConsoleReporter(oss) {}
+
+#define DOCTEST_DEBUG_OUTPUT_WINDOW_REPORTER_OVERRIDE(func, type) \
+ void func(type in) DOCTEST_OVERRIDE { \
+ if(isDebuggerActive()) { \
+ bool with_col = g_contextState->no_colors; \
+ g_contextState->no_colors = false; \
+ ConsoleReporter::func(in); \
+ myOutputDebugString(oss.str().c_str()); \
+ oss.str(""); \
+ g_contextState->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&)
+ };
+
+ DebugOutputWindowReporter g_debug_output_rep;
+
// the implementation of parseFlag()
bool parseFlagImpl(int argc, const char* const* argv, const char* pattern) {
for(int i = argc - 1; i >= 0; --i) {
@@ -1613,12 +1835,10 @@
// locates a flag on the command line
bool parseFlag(int argc, const char* const* argv, const char* pattern) {
#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- if(!parseFlagImpl(argc, argv, pattern))
- return parseFlagImpl(argc, argv, pattern + 3); // 3 for "dt-"
- return true;
-#else // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- return parseFlagImpl(argc, argv, pattern);
+ if(parseFlagImpl(argc, argv, pattern + 3)) // 3 to skip "dt-"
+ return true;
#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ return parseFlagImpl(argc, argv, pattern);
}
// the implementation of parseOption()
@@ -1653,12 +1873,10 @@
const String& defaultVal = String()) {
res = defaultVal;
#ifndef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- if(!parseOptionImpl(argc, argv, pattern, res))
- return parseOptionImpl(argc, argv, pattern + 3, res); // 3 for "dt-"
- return true;
-#else // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
- return parseOptionImpl(argc, argv, pattern, res);
+ if(parseOptionImpl(argc, argv, pattern + 3, res)) // 3 to skip "dt-"
+ return true;
#endif // DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
+ return parseOptionImpl(argc, argv, pattern, res);
}
// parses a comma separated list of words after a pattern in one of the arguments in argv
@@ -1720,129 +1938,9 @@
}
return false;
}
-
- void printVersion(std::ostream& s) {
- if(contextState->no_version == false)
- s << Color::Cyan << "[doctest] " << Color::None << "doctest version is \""
- << DOCTEST_VERSION_STR << "\"\n";
- }
-
- void printHelp(std::ostream& s) {
- printVersion(s);
- // clang-format off
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "boolean values: \"1/on/yes/true\" or \"0/off/no/false\"\n";
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "filter values: \"str1,str2,str3\" (comma separated strings)\n";
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "filters use wildcards for matching strings\n";
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "something passes a filter if any of the strings in a filter matches\n";
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "ALL FLAGS, OPTIONS AND FILTERS ALSO AVAILABLE WITH A \"dt-\" PREFIX!!!\n";
- s << Color::Cyan << "[doctest]\n" << Color::None;
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "Query flags - the program quits after them. Available:\n\n";
- s << " -?, --help, -h prints this message\n";
- s << " -v, --version prints the version\n";
- s << " -c, --count prints the number of matching tests\n";
- s << " -ltc, --list-test-cases lists all matching tests by name\n";
- s << " -lts, --list-test-suites lists all matching test suites\n\n";
- // ================================================================================== << 79
- s << Color::Cyan << "[doctest] " << Color::None;
- s << "The available <int>/<string> options/filters are:\n\n";
- s << " -tc, --test-case=<filters> filters tests by their name\n";
- s << " -tce, --test-case-exclude=<filters> filters OUT tests by their name\n";
- s << " -sf, --source-file=<filters> filters tests by their file\n";
- s << " -sfe, --source-file-exclude=<filters> filters OUT tests by their file\n";
- s << " -ts, --test-suite=<filters> filters tests by their test suite\n";
- s << " -tse, --test-suite-exclude=<filters> filters OUT tests by their test suite\n";
- s << " -sc, --subcase=<filters> filters subcases by their name\n";
- s << " -sce, --subcase-exclude=<filters> filters OUT subcases by their name\n";
- s << " -ob, --order-by=<string> how the tests should be ordered\n";
- s << " <string> - by [file/suite/name/rand]\n";
- s << " -rs, --rand-seed=<int> seed for random ordering\n";
- s << " -f, --first=<int> the first test passing the filters to\n";
- s << " execute - for range-based execution\n";
- s << " -l, --last=<int> the last test passing the filters to\n";
- s << " execute - for range-based execution\n";
- s << " -aa, --abort-after=<int> stop after <int> failed assertions\n";
- s << " -scfl,--subcase-filter-levels=<int> apply filters for the first <int> levels\n";
- s << Color::Cyan << "\n[doctest] " << Color::None;
- s << "Bool options - can be used like flags and true is assumed. Available:\n\n";
- s << " -s, --success=<bool> include successful assertions in output\n";
- s << " -cs, --case-sensitive=<bool> filters being treated as case sensitive\n";
- s << " -e, --exit=<bool> exits after the tests finish\n";
- s << " -d, --duration=<bool> prints the time duration of each test\n";
- s << " -nt, --no-throw=<bool> skips exceptions-related assert checks\n";
- s << " -ne, --no-exitcode=<bool> returns (or exits) always with success\n";
- s << " -nr, --no-run=<bool> skips all runtime doctest operations\n";
- s << " -nv, --no-version=<bool> omit the framework version in the output\n";
- s << " -nc, --no-colors=<bool> disables colors in output\n";
- s << " -fc, --force-colors=<bool> use colors even when not in a tty\n";
- s << " -nb, --no-breaks=<bool> disables breakpoints in debuggers\n";
- s << " -ns, --no-skip=<bool> don't skip test cases marked as skip\n";
- s << " -gfl, --gnu-file-line=<bool> :n: vs (n): for line numbers in output\n";
- s << " -npf, --no-path-filenames=<bool> only filenames and no paths in output\n";
- s << " -nln, --no-line-numbers=<bool> 0 instead of real line numbers in output\n";
- // ================================================================================== << 79
- // clang-format on
-
- s << Color::Cyan << "\n[doctest] " << Color::None;
- s << "for more information visit the project documentation\n\n";
- }
-
- void printSummary(std::ostream& s) {
- const ContextState* p = contextState;
-
- separator_to_stream(s);
-
- if(p->count || p->list_test_cases) {
- s << Color::Cyan << "[doctest] " << Color::None
- << "unskipped test cases passing the current filters: " << p->numTestsPassingFilters
- << "\n";
- } else if(p->list_test_suites) {
- s << Color::Cyan << "[doctest] " << Color::None
- << "unskipped test cases passing the current filters: " << p->numTestsPassingFilters
- << "\n";
- s << Color::Cyan << "[doctest] " << Color::None
- << "test suites with unskipped test cases passing the current filters: "
- << p->numTestSuitesPassingFilters << "\n";
- } else {
- const bool anythingFailed = p->numFailed > 0 || p->numFailedAssertions > 0;
- s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6)
- << p->numTestsPassingFilters << " | "
- << ((p->numTestsPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green)
- << std::setw(6) << p->numTestsPassingFilters - p->numFailed << " passed"
- << Color::None << " | " << (p->numFailed > 0 ? Color::Red : Color::None)
- << std::setw(6) << p->numFailed << " failed" << Color::None << " | ";
- if(p->no_skipped_summary == false) {
- const int numSkipped = static_cast<unsigned>(getRegisteredTests().size()) -
- p->numTestsPassingFilters;
- 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->numAssertions << " | "
- << ((p->numAssertions == 0 || anythingFailed) ? Color::None : Color::Green)
- << std::setw(6) << (p->numAssertions - p->numFailedAssertions) << " passed"
- << Color::None << " | " << (p->numFailedAssertions > 0 ? Color::Red : Color::None)
- << std::setw(6) << p->numFailedAssertions << " failed" << Color::None << " |\n";
- s << Color::Cyan << "[doctest] " << Color::None
- << "Status: " << (p->numFailed > 0 ? Color::Red : Color::Green)
- << ((p->numFailed > 0) ? "FAILURE!\n" : "SUCCESS!\n");
- }
-
- // remove any coloring
- s << Color::None;
- }
} // namespace detail
-bool isRunningInTest() { return detail::contextState != 0; }
+bool isRunningInTest() { return detail::g_contextState != 0; }
Context::Context(int argc, const char* const* argv)
: p(new detail::ContextState) {
@@ -1874,6 +1972,8 @@
parseCommaSepArgs(argc, argv, "dt-sc=", p->filters[6]);
parseCommaSepArgs(argc, argv, "dt-subcase-exclude=", p->filters[7]);
parseCommaSepArgs(argc, argv, "dt-sce=", p->filters[7]);
+ parseCommaSepArgs(argc, argv, "dt-reporters=", p->filters[8]);
+ parseCommaSepArgs(argc, argv, "dt-r=", p->filters[8]);
// clang-format on
int intRes = 0;
@@ -1904,8 +2004,8 @@
DOCTEST_PARSE_STR_OPTION("dt-order-by", "dt-ob", order_by, "file");
DOCTEST_PARSE_INT_OPTION("dt-rand-seed", "dt-rs", rand_seed, 0);
- DOCTEST_PARSE_INT_OPTION("dt-first", "dt-f", first, 1);
- DOCTEST_PARSE_INT_OPTION("dt-last", "dt-l", last, 0);
+ DOCTEST_PARSE_INT_OPTION("dt-first", "dt-f", first, 0);
+ DOCTEST_PARSE_INT_OPTION("dt-last", "dt-l", last, UINT_MAX);
DOCTEST_PARSE_INT_OPTION("dt-abort-after", "dt-aa", abort_after, 0);
DOCTEST_PARSE_INT_OPTION("dt-subcase-filter-levels", "dt-scfl", subcase_filter_levels, 2000000000);
@@ -1928,16 +2028,13 @@
DOCTEST_PARSE_AS_BOOL_OR_FLAG("dt-no-skipped-summary", "dt-nss", no_skipped_summary, false);
// clang-format on
-#undef DOCTEST_PARSE_STR_OPTION
-#undef DOCTEST_PARSE_INT_OPTION
-#undef DOCTEST_PARSE_AS_BOOL_OR_FLAG
-
if(withDefaults) {
p->help = false;
p->version = false;
p->count = false;
p->list_test_cases = false;
p->list_test_suites = false;
+ p->list_reporters = false;
}
if(parseFlag(argc, argv, "dt-help") || parseFlag(argc, argv, "dt-h") ||
parseFlag(argc, argv, "dt-?")) {
@@ -1960,6 +2057,10 @@
p->list_test_suites = true;
p->exit = true;
}
+ if(parseFlag(argc, argv, "dt-list-reporters") || parseFlag(argc, argv, "dt-lr")) {
+ p->list_reporters = true;
+ p->exit = true;
+ }
}
// allows the user to add procedurally to the filters from the command line
@@ -1990,33 +2091,46 @@
int Context::run() {
using namespace detail;
- Color::init();
-
- contextState = p;
+ g_contextState = p;
p->resetRunData();
- // handle version, help and no_run
- if(p->no_run || p->version || p->help) {
- if(p->version)
- printVersion(std::cout);
- if(p->help)
- printHelp(std::cout);
+ ConsoleReporterWithHelpers g_con_rep(std::cout);
+ registerReporter("console", 0, &g_con_rep);
- contextState = 0;
+ p->reporters_currently_used.clear();
+ if(p->filters[8].size() == 0)
+ p->reporters_currently_used.push_back(getReporters()[reporterMap::key_type(0, "console")]);
+ for(reporterMap::iterator it = getReporters().begin(); it != getReporters().end(); ++it) {
+ if(matchesAny(it->first.second.c_str(), p->filters[8], false, p->case_sensitive))
+ p->reporters_currently_used.push_back(it->second);
+ }
+
+#ifdef DOCTEST_PLATFORM_WINDOWS
+ if(isDebuggerActive())
+ p->reporters_currently_used.push_back(&g_debug_output_rep);
+#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();
+
+ g_contextState = 0;
return EXIT_SUCCESS;
}
- printVersion(std::cout);
- std::cout << Color::Cyan << "[doctest] " << Color::None << "run with \"--help\" for options\n";
+ g_con_rep.printIntro();
- unsigned i = 0; // counter used for loops - here for VC6
-
- std::set<TestCase>& registeredTests = getRegisteredTests();
+ std::set<TestCase>& all_tests = getRegisteredTests();
+ p->numTestCases = all_tests.size();
std::vector<const TestCase*> testArray;
- for(std::set<TestCase>::iterator it = registeredTests.begin(); it != registeredTests.end();
- ++it)
+ for(std::set<TestCase>::iterator it = all_tests.begin(); it != all_tests.end(); ++it)
testArray.push_back(&(*it));
// sort the collected records
@@ -2032,7 +2146,7 @@
// random_shuffle implementation
const TestCase** first = &testArray[0];
- for(i = testArray.size() - 1; i > 0; --i) {
+ for(size_t i = testArray.size() - 1; i > 0; --i) {
int idxToSwap = std::rand() % (i + 1); // NOLINT
const TestCase* temp = first[i];
@@ -2043,38 +2157,52 @@
}
}
- if(p->list_test_cases) {
- std::cout << Color::Cyan << "[doctest] " << Color::None << "listing all test case names\n";
- separator_to_stream(std::cout);
- }
+ if(p->list_test_cases)
+ g_con_rep.output_query_preamble_test_cases();
- std::set<String> testSuitesPassingFilters;
- if(p->list_test_suites) {
- std::cout << Color::Cyan << "[doctest] " << Color::None << "listing all test suites\n";
- separator_to_stream(std::cout);
- }
+ std::set<String> testSuitesPassingFilt;
+ if(p->list_test_suites)
+ g_con_rep.output_query_preamble_test_suites();
+
+ bool query_mode = p->count || p->list_test_cases || p->list_test_suites;
+
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_start, *g_contextState);
// invoke the registered functions if they match the filter criteria (or just count them)
- for(i = 0; i < testArray.size(); i++) {
- const TestCase& data = *testArray[i];
+ for(size_t i = 0; i < testArray.size(); i++) {
+ const TestCase& tc = *testArray[i];
- if(data.m_skip && !p->no_skip)
- continue;
+ bool skip_me = false;
+ if(tc.m_skip && !p->no_skip)
+ skip_me = true;
- if(!matchesAny(data.m_file, p->filters[0], 1, p->case_sensitive))
- continue;
- if(matchesAny(data.m_file, p->filters[1], 0, p->case_sensitive))
- continue;
- if(!matchesAny(data.m_test_suite, p->filters[2], 1, p->case_sensitive))
- continue;
- if(matchesAny(data.m_test_suite, p->filters[3], 0, p->case_sensitive))
- continue;
- if(!matchesAny(data.m_name, p->filters[4], 1, p->case_sensitive))
- continue;
- if(matchesAny(data.m_name, p->filters[5], 0, p->case_sensitive))
- continue;
+ if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_test_suite, p->filters[3], false, p->case_sensitive))
+ skip_me = true;
+ if(!matchesAny(tc.m_name, p->filters[4], true, p->case_sensitive))
+ skip_me = true;
+ if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive))
+ skip_me = true;
- p->numTestsPassingFilters++;
+ if(!skip_me)
+ p->numTestCasesPassingFilters++;
+
+ // skip the test if it is not in the execution range
+ if((p->last < p->numTestCasesPassingFilters && p->first <= p->last) ||
+ (p->first > p->numTestCasesPassingFilters))
+ skip_me = true;
+
+ if(skip_me) {
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_skipped, tc);
+ continue;
+ }
// do not execute the test if we are to only count the number of filter passing tests
if(p->count)
@@ -2082,152 +2210,143 @@
// print the name of the test and don't execute it
if(p->list_test_cases) {
- std::cout << Color::None << data.m_name << "\n";
+ g_con_rep.output_c_string_with_newline(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((testSuitesPassingFilters.count(data.m_test_suite) == 0) &&
- data.m_test_suite[0] != '\0') {
- std::cout << Color::None << data.m_test_suite << "\n";
- testSuitesPassingFilters.insert(data.m_test_suite);
+ 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);
+ testSuitesPassingFilt.insert(tc.m_test_suite);
p->numTestSuitesPassingFilters++;
}
continue;
}
- // skip the test if it is not in the execution range
- if((p->last < p->numTestsPassingFilters && p->first <= p->last) ||
- (p->first > p->numTestsPassingFilters))
- continue;
-
// execute the test if it passes all the filtering
{
- p->currentTest = &data;
+ p->currentTest = &tc;
- bool failed = false;
- p->hasLoggedCurrentTestStart = false;
- p->numFailedAssertionsForCurrentTestcase = 0;
+ p->failure_flags = TestCaseFailureReason::None;
+ p->numAssertsFailedForCurrentTestCase = 0;
+ p->numAssertsForCurrentTestCase = 0;
+ p->seconds_so_far = 0;
+ p->error_string = "";
+
p->subcasesPassed.clear();
- double duration = 0;
- Timer timer;
- timer.start();
do {
- // if the start has been logged from a previous iteration of this loop
- if(p->hasLoggedCurrentTestStart)
- logTestEnd();
- p->hasLoggedCurrentTestStart = false;
-
- // if logging successful tests - force the start log
- if(p->success)
- DOCTEST_LOG_START;
-
- // reset the assertion state
- p->numAssertionsForCurrentTestcase = 0;
- p->hasCurrentTestFailed = false;
-
// reset some of the fields for subcases (except for the set of fully passed ones)
- p->subcasesHasSkipped = false;
+ p->should_reenter = false;
p->subcasesCurrentLevel = 0;
p->subcasesEnteredLevels.clear();
// reset stuff for logging with INFO()
- p->exceptionalContexts.clear();
+ p->stringifiedContexts.clear();
-// execute the test
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_start, tc);
+
+ g_timer.start();
+
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
try {
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
FatalConditionHandler fatalConditionHandler; // Handle signals
- data.m_test();
+ // execute the test
+ tc.m_test();
fatalConditionHandler.reset();
- if(contextState->hasCurrentTestFailed)
- failed = true;
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
- } catch(const TestFailureException&) { failed = true; } catch(...) {
- DOCTEST_LOG_START;
- DOCTEST_TO_CONSOLE_AND_OUTPUT_WINDOW(
- logTestException,
- DOCTEST_COMMA * contextState->currentTest DOCTEST_COMMA
- translateActiveException() DOCTEST_COMMA false);
-
- failed = true;
+ } catch(const TestFailureException&) {
+ p->failure_flags |= TestCaseFailureReason::AssertFailure;
+ } catch(...) {
+ p->error_string = translateActiveException();
+ p->failure_flags |= TestCaseFailureReason::Exception;
}
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
- p->numAssertions += p->numAssertionsForCurrentTestcase;
+ p->seconds_so_far += g_timer.getElapsedSeconds();
- // exit this loop if enough assertions have failed
- if(p->abort_after > 0 && p->numFailedAssertions >= p->abort_after) {
- p->subcasesHasSkipped = false;
- std::cout << Color::Red << "Aborting - too many failed asserts!\n";
+ // exit this loop if enough assertions have failed - even if there are more subcases
+ if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after) {
+ p->should_reenter = false;
+ p->failure_flags |= TestCaseFailureReason::TooManyFailedAsserts;
}
- } while(p->subcasesHasSkipped == true);
+ // 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_contextState);
+ } while(p->should_reenter == true);
- duration = timer.getElapsedSeconds();
+ if(g_contextState->numAssertsFailedForCurrentTestCase)
+ p->failure_flags |= TestCaseFailureReason::AssertFailure;
if(Approx(p->currentTest->m_timeout).epsilon(DBL_EPSILON) != 0 &&
- Approx(duration).epsilon(DBL_EPSILON) > p->currentTest->m_timeout) {
- failed = true;
- DOCTEST_LOG_START;
- std::cout << Color::Red << "Test case exceeded time limit of "
- << std::setprecision(6) << std::fixed << p->currentTest->m_timeout
- << "!\n";
- }
+ Approx(p->seconds_so_far).epsilon(DBL_EPSILON) > p->currentTest->m_timeout)
+ p->failure_flags |= TestCaseFailureReason::Timeout;
- if(p->duration)
- std::cout << Color::None << std::setprecision(6) << std::fixed << duration
- << " s: " << p->currentTest->m_name << "\n";
-
- if(data.m_should_fail) {
- DOCTEST_LOG_START;
- if(failed)
- std::cout << Color::Yellow
- << "Failed as expected so marking it as not failed\n";
- else
- std::cout << Color::Red
- << "Should have failed but didn't! Marking it as failed!\n";
- failed = !failed;
- } else if(failed && data.m_may_fail) {
- DOCTEST_LOG_START;
- failed = false;
- std::cout << Color::Yellow << "Allowed to fail so marking it as not failed\n";
- } else if(data.m_expected_failures > 0) {
- DOCTEST_LOG_START;
- if(p->numFailedAssertionsForCurrentTestcase == data.m_expected_failures) {
- failed = false;
- std::cout << Color::Yellow << "Failed exactly " << data.m_expected_failures
- << " times as expected so marking it as not failed!\n";
+ if(tc.m_should_fail) {
+ if(p->failure_flags) {
+ p->failure_flags |= TestCaseFailureReason::ShouldHaveFailedAndDid;
} else {
- failed = true;
- std::cout << Color::Red << "Didn't fail exactly " << data.m_expected_failures
- << " times so marking it as failed!\n";
+ 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;
}
}
- std::cout << Color::None;
- if(p->hasLoggedCurrentTestStart)
- logTestEnd();
+ bool ok_to_fail = (TestCaseFailureReason::ShouldHaveFailedAndDid & p->failure_flags) ||
+ (TestCaseFailureReason::CouldHaveFailedAndDid & p->failure_flags) ||
+ (TestCaseFailureReason::FailedExactlyNumTimes & p->failure_flags);
- if(failed) // if any subcase has failed - the whole test case has failed
- p->numFailed++;
+ // if any subcase has failed - the whole test case has failed
+ if(p->failure_flags && !ok_to_fail)
+ p->numTestCasesFailed++;
+
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_case_end, *g_contextState);
// stop executing tests if enough assertions have failed
- if(p->abort_after > 0 && p->numFailedAssertions >= p->abort_after)
+ if(p->abort_after > 0 && p->numAssertsFailed >= p->abort_after)
break;
}
}
- printSummary(std::cout);
+ if(!query_mode)
+ DOCTEST_ITERATE_THROUGH_REPORTERS(test_run_end, *g_contextState);
+ else
+ g_con_rep.output_query_results();
- contextState = 0;
+ g_contextState = 0;
- if(p->numFailed && !p->no_exitcode)
+ if(p->numTestCasesFailed && !p->no_exitcode)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
+
+int IReporter::get_num_active_contexts() { return detail::g_contextState->contexts.size(); }
+const IContextScope* const* IReporter::get_active_contexts() {
+ return get_num_active_contexts() ? &detail::g_contextState->contexts[0] : 0;
+}
+
+int IReporter::get_num_stringified_contexts() {
+ return detail::g_contextState->stringifiedContexts.size();
+}
+const String* IReporter::get_stringified_contexts() {
+ return get_num_stringified_contexts() ? &detail::g_contextState->stringifiedContexts[0] : 0;
+}
+
+int registerReporter(const char* name, int priority, IReporter* r) {
+ detail::getReporters().insert(
+ detail::reporterMap::value_type(detail::reporterMap::key_type(priority, name), r));
+ return 0;
+}
+
} // namespace doctest
#endif // DOCTEST_CONFIG_DISABLE
diff --git a/examples/all_features/coverage_maxout.cpp b/examples/all_features/coverage_maxout.cpp
index 27ca03f..eaf8771 100644
--- a/examples/all_features/coverage_maxout.cpp
+++ b/examples/all_features/coverage_maxout.cpp
@@ -18,10 +18,10 @@
{
namespace detail
{
- const char* fileForOutput(const char* file);
+ //const char* fileForOutput(const char* file);
void reportFatal(const std::string&);
int wildcmp(const char* str, const char* wild, bool caseSensitive);
- void myOutputDebugString(const String&);
+ void myOutputDebugString(const char*);
} // namespace detail
} // namespace doctest
@@ -32,7 +32,7 @@
detail::myOutputDebugString("");
// trigger code path for comparing the file in "operator<" of SubcaseSignature
- CHECK(detail::SubcaseSignature("", "a.cpp", 0) < detail::SubcaseSignature("", "b.cpp", 0));
+ CHECK(SubcaseSignature("", "a.cpp", 0) < SubcaseSignature("", "b.cpp", 0));
// same for String
CHECK(String("a.cpp") < String("b.cpp"));
@@ -60,9 +60,9 @@
+ toString(static_cast<unsigned short>(1));
// others
- a += detail::fileForOutput("c:\\a");
- a += detail::fileForOutput("c:/a");
- a += detail::fileForOutput("a");
+ //a += detail::fileForOutput("c:\\a");
+ //a += detail::fileForOutput("c:/a");
+ //a += detail::fileForOutput("a");
std::ostringstream oss;
@@ -82,7 +82,7 @@
// trigger code path for String to ostream through operator<<
oss << a;
// trigger code path for assert string of a non-existent assert type
- oss << detail::assertString(static_cast<detail::assertType::Enum>(3));
+ oss << assertString(static_cast<assertType::Enum>(3));
a += oss.str().c_str();
// trigger code path for rawMemoryToString
bool isThereAnything = a.size() > 0u;
diff --git a/examples/all_features/header.h b/examples/all_features/header.h
index a9ccd66..116dc50 100644
--- a/examples/all_features/header.h
+++ b/examples/all_features/header.h
@@ -46,6 +46,8 @@
DOCTEST_GCC_SUPPRESS_WARNING("-Wstrict-overflow")
#endif // gcc 5
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(26439) // This kind of function may not throw. Declare it 'noexcept'
+
struct SomeFixture
{
int data;
@@ -59,6 +61,8 @@
}
};
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
+
TEST_CASE_FIXTURE(SomeFixture, "fixtured test") {
data /= 2;
CHECK(data == 21);
diff --git a/examples/all_features/test_output/help.txt b/examples/all_features/test_output/help.txt
index bd5a793..8502955 100644
--- a/examples/all_features/test_output/help.txt
+++ b/examples/all_features/test_output/help.txt
@@ -14,6 +14,7 @@
-c, --count prints the number of matching tests
-ltc, --list-test-cases lists all matching tests by name
-lts, --list-test-suites lists all matching test suites
+ -lr, --list-reporters lists all registered reporters
[doctest] The available <int>/<string> options/filters are:
@@ -25,6 +26,7 @@
-tse, --test-suite-exclude=<filters> filters OUT tests by their test suite
-sc, --subcase=<filters> filters subcases by their name
-sce, --subcase-exclude=<filters> filters OUT subcases by their name
+ -r, --reporters=<filters> reporters to use (console is default)
-ob, --order-by=<string> how the tests should be ordered
<string> - by [file/suite/name/rand]
-rs, --rand-seed=<int> seed for random ordering
diff --git a/scripts/random_dev_notes.md b/scripts/random_dev_notes.md
index 7a6489c..73e0208 100644
--- a/scripts/random_dev_notes.md
+++ b/scripts/random_dev_notes.md
@@ -1,4 +1,75 @@
+the current reporter interface
+ - can be used for listening for events
+ - multiple reporters can be used
+ - custom reporters can be written
+ - register and choose reporters
+ - list all reporters
+todo:
+ - output to file (or just not stdout)
+ - xml output
+ - xUnit reporter
+ - compact reporter
+ - progress reporter
+ - options
+ - absolutely no output on success
+ - summary only
+
+https://www.boost.org/doc/libs/1_67_0/libs/test/doc/html/index.html
+
+
+
+
+
+
+examples
+
+c++11 only tests
+
+thread sanitizer tests
+
+documentation... :(
+
+profit :D
+
+
+
+ask in the reddit thread what from the roadmap they would like to see next
+
+
+
+
+OMG!!! THIS!!!
+https://github.com/onqtam/doctest/issues/114
+isRunningInTest()
+
+
+
+
+
+a failing REQUIRE inside of a CHECK
+https://github.com/catchorg/Catch2/issues/1292
+
+int foo(int i) {
+ REQUIRE(i > 10);
+ return 42;
+}
+TEST_CASE("a") {
+ CHECK(foo(2) == 2);
+}
+
+
+
+look at CHECKED_IF & friends
+https://github.com/catchorg/Catch2/issues/1278
+
+
+
+
+user command line options...?
+
+
+
move more inline functions from fwd to impl (like Approx stuff, also the inline string stuff...), also ifdef can_use_op<> & friends
https://github.com/catchorg/Catch2/commit/de36b2ada6e4593a9a32c4c86cd47d4bc002b148