Nan check (#582)
* CHECK_NAN (Resolves #105)
* Fix helper macro
* Include correct test header
* Move definitions to cpp file
* Extern declaration and exception fix
* Fix more warnings
* Warning suppression
* Add NaN checking docs
* Improved wording
diff --git a/doc/markdown/assertions.md b/doc/markdown/assertions.md
index bdd33af..6c8e29a 100644
--- a/doc/markdown/assertions.md
+++ b/doc/markdown/assertions.md
@@ -133,6 +133,17 @@
Currently [**logging macros**](logging.md) cannot be used for extra context for asserts outside of a test run. That means that the ```_MESSAGE``` variants of asserts are also not usable - since they are just a packed ```INFO()``` with an assert right after it.
+## NaN checking
+
+```<LEVEL>``` is one of 3 possible: ```REQUIRE```/```CHECK```/```WARN```.
+
+- ```<LEVEL>_NAN(expression)```
+- ```<LEVEL>_NOT_NAN(expression)```
+
+These utility macros check if a floating point value is or is not NaN respectively.
+
+They capture the actual float value on assertion failure.
+
## Floating point comparisons
When comparing floating point numbers - especially if at least one of them has been computed - great care must be taken to allow for rounding errors and inexact representations.
diff --git a/doctest/doctest.h b/doctest/doctest.h
index d25f526..da01f1c 100644
--- a/doctest/doctest.h
+++ b/doctest/doctest.h
@@ -231,7 +231,8 @@
DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \
DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \
DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \
- DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */
+ DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */
#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
@@ -444,6 +445,7 @@
#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#include <cmath>
#include <cstddef>
#include <ostream>
#include <istream>
@@ -631,6 +633,8 @@
is_ge = 2 * is_gt,
is_le = 2 * is_ge,
+ is_nan = 2 * is_le,
+
// macro types
DT_WARN = is_normal | is_warn,
@@ -692,6 +696,14 @@
DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn,
DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check,
DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require,
+
+ DT_WARN_NAN = is_normal | is_nan | is_warn,
+ DT_CHECK_NAN = is_normal | is_nan | is_check,
+ DT_REQUIRE_NAN = is_normal | is_nan | is_require,
+
+ DT_WARN_NOT_NAN = is_normal | is_nan | is_false | is_warn,
+ DT_CHECK_NOT_NAN = is_normal | is_nan | is_false | is_check,
+ DT_REQUIRE_NOT_NAN = is_normal | is_nan | is_false | is_require,
};
} // namespace assertType
@@ -1489,6 +1501,12 @@
DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge)
DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le)
+ template <typename T>
+ bool is_nan(T);
+ extern template bool is_nan(float);
+ extern template bool is_nan(double);
+ extern template bool is_nan(long double);
+
struct DOCTEST_INTERFACE ResultBuilder : public AssertData
{
ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
@@ -1518,6 +1536,19 @@
return !m_failed;
}
+ template <typename L>
+ DOCTEST_NOINLINE bool nan_assert(L val) {
+ m_failed = !is_nan(val);
+
+ if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ m_failed = !m_failed;
+
+ if(m_failed || getContextOptions()->success)
+ m_decomp = toString(val);
+
+ return !m_failed;
+ }
+
void translateException();
bool log();
@@ -1597,6 +1628,23 @@
return !failed;
}
+ template <typename L>
+ DOCTEST_NOINLINE bool nan_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, L val) {
+ bool failed = !is_nan(val);
+
+ if (at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ failed = !failed;
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(toString(val));
+ DOCTEST_ASSERT_IN_TESTS(toString(val));
+ return !failed;
+ }
+
struct DOCTEST_INTERFACE IExceptionTranslator
{
IExceptionTranslator();
@@ -2382,6 +2430,32 @@
#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_NAN_ASSERT(assert_type, ...) \
+ [&] { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY( \
+ DOCTEST_RB.nan_assert(__VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ }()
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_NAN_ASSERT(assert_type, ...) \
+ doctest::detail::nan_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
+ #__VA_ARGS__, __VA_ARGS__)
+
+#endif
+
+#define DOCTEST_WARN_NAN(...) DOCTEST_NAN_ASSERT(DT_WARN_NAN, __VA_ARGS__)
+#define DOCTEST_CHECK_NAN(...) DOCTEST_NAN_ASSERT(DT_CHECK_NAN, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NAN(...) DOCTEST_NAN_ASSERT(DT_REQUIRE_NAN, __VA_ARGS__)
+#define DOCTEST_WARN_NOT_NAN(...) DOCTEST_NAN_ASSERT(DT_WARN_NOT_NAN, __VA_ARGS__)
+#define DOCTEST_CHECK_NOT_NAN(...) DOCTEST_NAN_ASSERT(DT_CHECK_NOT_NAN, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NOT_NAN(...) DOCTEST_NAN_ASSERT(DT_REQUIRE_NOT_NAN, __VA_ARGS__)
+
#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
#undef DOCTEST_WARN_THROWS
@@ -2600,6 +2674,12 @@
#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_WARN_NAN(...) [&] { return doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NAN(...) [&] { return doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NAN(...) [&] { return doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_WARN_NOT_NAN(...) [&] { return !doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NOT_NAN(...) [&] { return !doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NOT_NAN(...) [&] { return !doctest::detail::is_nan(__VA_ARGS__); }()
#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
@@ -2643,6 +2723,13 @@
#define DOCTEST_CHECK_UNARY_FALSE(...) ([] { return false; })
#define DOCTEST_REQUIRE_UNARY_FALSE(...) ([] { return false; })
+#define DOCTEST_WARN_NAN(...) [&] { return false; }()
+#define DOCTEST_CHECK_NAN(...) [&] { return false; }()
+#define DOCTEST_REQUIRE_NAN(...) [&] { return false; }()
+#define DOCTEST_WARN_NOT_NAN(...) [&] { return false; }()
+#define DOCTEST_CHECK_NOT_NAN(...) [&] { return false; }()
+#define DOCTEST_REQUIRE_NOT_NAN(...) [&] { return false; }()
+
#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
// TODO: think about if these also need to work properly even when doctest is disabled
@@ -2832,6 +2919,13 @@
#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__)
#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+#define WARN_NAN(...) DOCTEST_WARN_NAN(__VA_ARGS__)
+#define CHECK_NAN(...) DOCTEST_CHECK_NAN(__VA_ARGS__)
+#define REQUIRE_NAN(...) DOCTEST_REQUIRE_NAN(__VA_ARGS__)
+#define WARN_NOT_NAN(...) DOCTEST_WARN_NOT_NAN(__VA_ARGS__)
+#define CHECK_NOT_NAN(...) DOCTEST_CHECK_NOT_NAN(__VA_ARGS__)
+#define REQUIRE_NOT_NAN(...) DOCTEST_REQUIRE_NOT_NAN(__VA_ARGS__)
+
// KEPT FOR BACKWARDS COMPATIBILITY
#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__)
#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__)
@@ -3603,64 +3697,45 @@
// clang-format off
const char* assertString(assertType::Enum at) {
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled
- switch(at) { //!OCLINT missing default in switch statements
- case assertType::DT_WARN : return "WARN";
- case assertType::DT_CHECK : return "CHECK";
- case assertType::DT_REQUIRE : return "REQUIRE";
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitely handled
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type)
+ switch(at) {
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS);
- case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH";
- case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH";
- case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH);
- case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS";
- case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS";
- case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NAN);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOT_NAN);
+
+ default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!");
}
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- return "";
}
// clang-format on
@@ -4650,6 +4725,16 @@
} // namespace
namespace detail {
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738)
+ template <typename T>
+ bool is_nan(T t) {
+ return std::isnan(t);
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ template bool is_nan(float);
+ template bool is_nan(double);
+ template bool is_nan(long double);
+
ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
const char* exception_type, const char* exception_string) {
m_test_case = g_cs->currentTest;
diff --git a/doctest/parts/doctest.cpp b/doctest/parts/doctest.cpp
index 77dd994..3cf867f 100644
--- a/doctest/parts/doctest.cpp
+++ b/doctest/parts/doctest.cpp
@@ -697,64 +697,45 @@
// clang-format off
const char* assertString(assertType::Enum at) {
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4062) // enum 'x' in switch of enum 'y' is not handled
- switch(at) { //!OCLINT missing default in switch statements
- case assertType::DT_WARN : return "WARN";
- case assertType::DT_CHECK : return "CHECK";
- case assertType::DT_REQUIRE : return "REQUIRE";
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4061) // enum 'x' in switch of enum 'y' is not explicitely handled
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASE(assert_type) case assertType::DT_ ## assert_type: return #assert_type
+ #define DOCTEST_GENERATE_ASSERT_TYPE_CASES(assert_type) \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK_ ## assert_type); \
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(REQUIRE_ ## assert_type)
+ switch(at) {
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(WARN);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(CHECK);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASE(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_AS);
- case assertType::DT_WARN_THROWS_WITH : return "WARN_THROWS_WITH";
- case assertType::DT_CHECK_THROWS_WITH : return "CHECK_THROWS_WITH";
- case assertType::DT_REQUIRE_THROWS_WITH : return "REQUIRE_THROWS_WITH";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH);
- case assertType::DT_WARN_THROWS_WITH_AS : return "WARN_THROWS_WITH_AS";
- case assertType::DT_CHECK_THROWS_WITH_AS : return "CHECK_THROWS_WITH_AS";
- case assertType::DT_REQUIRE_THROWS_WITH_AS : return "REQUIRE_THROWS_WITH_AS";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(THROWS_WITH_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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(EQ);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(LT);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(GE);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(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";
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(UNARY_FALSE);
+
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NAN);
+ DOCTEST_GENERATE_ASSERT_TYPE_CASES(NOT_NAN);
+
+ default: DOCTEST_INTERNAL_ERROR("Tried stringifying invalid assert type!");
}
DOCTEST_MSVC_SUPPRESS_WARNING_POP
- return "";
}
// clang-format on
@@ -1744,6 +1725,16 @@
} // namespace
namespace detail {
+ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4738)
+ template <typename T>
+ bool is_nan(T t) {
+ return std::isnan(t);
+ }
+ DOCTEST_MSVC_SUPPRESS_WARNING_POP
+ template bool is_nan(float);
+ template bool is_nan(double);
+ template bool is_nan(long double);
+
ResultBuilder::ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
const char* exception_type, const char* exception_string) {
m_test_case = g_cs->currentTest;
diff --git a/doctest/parts/doctest_fwd.h b/doctest/parts/doctest_fwd.h
index b0d786f..dfd0545 100644
--- a/doctest/parts/doctest_fwd.h
+++ b/doctest/parts/doctest_fwd.h
@@ -228,7 +228,8 @@
DOCTEST_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \
DOCTEST_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \
DOCTEST_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \
- DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */
+ DOCTEST_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \
+ DOCTEST_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */
#define DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END DOCTEST_MSVC_SUPPRESS_WARNING_POP
@@ -441,6 +442,7 @@
#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
+#include <cmath>
#include <cstddef>
#include <ostream>
#include <istream>
@@ -628,6 +630,8 @@
is_ge = 2 * is_gt,
is_le = 2 * is_ge,
+ is_nan = 2 * is_le,
+
// macro types
DT_WARN = is_normal | is_warn,
@@ -689,6 +693,14 @@
DT_WARN_UNARY_FALSE = is_normal | is_false | is_unary | is_warn,
DT_CHECK_UNARY_FALSE = is_normal | is_false | is_unary | is_check,
DT_REQUIRE_UNARY_FALSE = is_normal | is_false | is_unary | is_require,
+
+ DT_WARN_NAN = is_normal | is_nan | is_warn,
+ DT_CHECK_NAN = is_normal | is_nan | is_check,
+ DT_REQUIRE_NAN = is_normal | is_nan | is_require,
+
+ DT_WARN_NOT_NAN = is_normal | is_nan | is_false | is_warn,
+ DT_CHECK_NOT_NAN = is_normal | is_nan | is_false | is_check,
+ DT_REQUIRE_NOT_NAN = is_normal | is_nan | is_false | is_require,
};
} // namespace assertType
@@ -1486,6 +1498,12 @@
DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge)
DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le)
+ template <typename T>
+ bool is_nan(T);
+ extern template bool is_nan(float);
+ extern template bool is_nan(double);
+ extern template bool is_nan(long double);
+
struct DOCTEST_INTERFACE ResultBuilder : public AssertData
{
ResultBuilder(assertType::Enum at, const char* file, int line, const char* expr,
@@ -1515,6 +1533,19 @@
return !m_failed;
}
+ template <typename L>
+ DOCTEST_NOINLINE bool nan_assert(L val) {
+ m_failed = !is_nan(val);
+
+ if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ m_failed = !m_failed;
+
+ if(m_failed || getContextOptions()->success)
+ m_decomp = toString(val);
+
+ return !m_failed;
+ }
+
void translateException();
bool log();
@@ -1594,6 +1625,23 @@
return !failed;
}
+ template <typename L>
+ DOCTEST_NOINLINE bool nan_assert(assertType::Enum at, const char* file, int line,
+ const char* expr, L val) {
+ bool failed = !is_nan(val);
+
+ if (at & assertType::is_false) //!OCLINT bitwise operator in conditional
+ failed = !failed;
+
+ // ###################################################################################
+ // IF THE DEBUGGER BREAKS HERE - GO 1 LEVEL UP IN THE CALLSTACK FOR THE FAILING ASSERT
+ // THIS IS THE EFFECT OF HAVING 'DOCTEST_CONFIG_SUPER_FAST_ASSERTS' DEFINED
+ // ###################################################################################
+ DOCTEST_ASSERT_OUT_OF_TESTS(toString(val));
+ DOCTEST_ASSERT_IN_TESTS(toString(val));
+ return !failed;
+ }
+
struct DOCTEST_INTERFACE IExceptionTranslator
{
IExceptionTranslator();
@@ -2379,6 +2427,32 @@
#define DOCTEST_CHECK_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_CHECK_UNARY_FALSE, __VA_ARGS__)
#define DOCTEST_REQUIRE_UNARY_FALSE(...) DOCTEST_UNARY_ASSERT(DT_REQUIRE_UNARY_FALSE, __VA_ARGS__)
+#ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_NAN_ASSERT(assert_type, ...) \
+ [&] { \
+ doctest::detail::ResultBuilder DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \
+ __LINE__, #__VA_ARGS__); \
+ DOCTEST_WRAP_IN_TRY( \
+ DOCTEST_RB.nan_assert(__VA_ARGS__)) \
+ DOCTEST_ASSERT_LOG_REACT_RETURN(DOCTEST_RB); \
+ }()
+
+#else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS
+
+#define DOCTEST_NAN_ASSERT(assert_type, ...) \
+ doctest::detail::nan_assert(doctest::assertType::assert_type, __FILE__, __LINE__, \
+ #__VA_ARGS__, __VA_ARGS__)
+
+#endif
+
+#define DOCTEST_WARN_NAN(...) DOCTEST_NAN_ASSERT(DT_WARN_NAN, __VA_ARGS__)
+#define DOCTEST_CHECK_NAN(...) DOCTEST_NAN_ASSERT(DT_CHECK_NAN, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NAN(...) DOCTEST_NAN_ASSERT(DT_REQUIRE_NAN, __VA_ARGS__)
+#define DOCTEST_WARN_NOT_NAN(...) DOCTEST_NAN_ASSERT(DT_WARN_NOT_NAN, __VA_ARGS__)
+#define DOCTEST_CHECK_NOT_NAN(...) DOCTEST_NAN_ASSERT(DT_CHECK_NOT_NAN, __VA_ARGS__)
+#define DOCTEST_REQUIRE_NOT_NAN(...) DOCTEST_NAN_ASSERT(DT_REQUIRE_NOT_NAN, __VA_ARGS__)
+
#ifdef DOCTEST_CONFIG_NO_EXCEPTIONS
#undef DOCTEST_WARN_THROWS
@@ -2597,6 +2671,12 @@
#define DOCTEST_WARN_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
#define DOCTEST_CHECK_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
#define DOCTEST_REQUIRE_UNARY_FALSE(...) [&] { return !(__VA_ARGS__); }()
+#define DOCTEST_WARN_NAN(...) [&] { return doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NAN(...) [&] { return doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NAN(...) [&] { return doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_WARN_NOT_NAN(...) [&] { return !doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_CHECK_NOT_NAN(...) [&] { return !doctest::detail::is_nan(__VA_ARGS__); }()
+#define DOCTEST_REQUIRE_NOT_NAN(...) [&] { return !doctest::detail::is_nan(__VA_ARGS__); }()
#else // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
@@ -2640,6 +2720,13 @@
#define DOCTEST_CHECK_UNARY_FALSE(...) ([] { return false; })
#define DOCTEST_REQUIRE_UNARY_FALSE(...) ([] { return false; })
+#define DOCTEST_WARN_NAN(...) [&] { return false; }()
+#define DOCTEST_CHECK_NAN(...) [&] { return false; }()
+#define DOCTEST_REQUIRE_NAN(...) [&] { return false; }()
+#define DOCTEST_WARN_NOT_NAN(...) [&] { return false; }()
+#define DOCTEST_CHECK_NOT_NAN(...) [&] { return false; }()
+#define DOCTEST_REQUIRE_NOT_NAN(...) [&] { return false; }()
+
#endif // DOCTEST_CONFIG_EVALUATE_ASSERTS_EVEN_WHEN_DISABLED
// TODO: think about if these also need to work properly even when doctest is disabled
@@ -2829,6 +2916,13 @@
#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__)
#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+#define WARN_NAN(...) DOCTEST_WARN_NAN(__VA_ARGS__)
+#define CHECK_NAN(...) DOCTEST_CHECK_NAN(__VA_ARGS__)
+#define REQUIRE_NAN(...) DOCTEST_REQUIRE_NAN(__VA_ARGS__)
+#define WARN_NOT_NAN(...) DOCTEST_WARN_NOT_NAN(__VA_ARGS__)
+#define CHECK_NOT_NAN(...) DOCTEST_CHECK_NOT_NAN(__VA_ARGS__)
+#define REQUIRE_NOT_NAN(...) DOCTEST_REQUIRE_NOT_NAN(__VA_ARGS__)
+
// KEPT FOR BACKWARDS COMPATIBILITY
#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__)
#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__)
diff --git a/examples/all_features/assertion_macros.cpp b/examples/all_features/assertion_macros.cpp
index d2f0ada..8dcebb9 100644
--- a/examples/all_features/assertion_macros.cpp
+++ b/examples/all_features/assertion_macros.cpp
@@ -4,6 +4,8 @@
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <stdexcept>
+#include <cmath>
+#include <limits>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
TEST_CASE("normal macros") {
@@ -204,3 +206,11 @@
if (CHECK_THROWS([]{}())) { MESSAGE("should not be reached!"); }
DOCTEST_MSVC_SUPPRESS_WARNING_POP
}
+
+TEST_CASE("nan") {
+ REQUIRE_NOT_NAN(0.f);
+ CHECK_NAN(std::numeric_limits<long double>::infinity());
+ CHECK_NOT_NAN(0.);
+ WARN_NOT_NAN(std::numeric_limits<float>::quiet_NaN());
+ REQUIRE_NAN(std::numeric_limits<long double>::signaling_NaN());
+}
diff --git a/examples/all_features/asserts_used_outside_of_tests.cpp b/examples/all_features/asserts_used_outside_of_tests.cpp
index 289c266..b427348 100644
--- a/examples/all_features/asserts_used_outside_of_tests.cpp
+++ b/examples/all_features/asserts_used_outside_of_tests.cpp
@@ -22,6 +22,7 @@
CHECK(false);
CHECK_THROWS(std::cout << "hello! \n");
+ CHECK_NAN(0.);
}
// std::mutex g_mut;
diff --git a/examples/all_features/test_output/assertion_macros.cpp.txt b/examples/all_features/test_output/assertion_macros.cpp.txt
index e377396..0366010 100644
--- a/examples/all_features/test_output/assertion_macros.cpp.txt
+++ b/examples/all_features/test_output/assertion_macros.cpp.txt
@@ -219,7 +219,17 @@
assertion_macros.cpp(0): ERROR: CHECK_THROWS( []{}() ) did NOT throw at all!
===============================================================================
-[doctest] test cases: 22 | 3 passed | 19 failed |
-[doctest] assertions: 80 | 35 passed | 45 failed |
+assertion_macros.cpp(0):
+TEST CASE: nan
+
+assertion_macros.cpp(0): ERROR: CHECK_NAN( std::numeric_limits<long double>::infinity() ) is NOT correct!
+ values: CHECK_NAN( inf )
+
+assertion_macros.cpp(0): WARNING: WARN_NOT_NAN( std::numeric_limits<float>::quiet_NaN() ) is NOT correct!
+ values: WARN_NOT_NAN( nanf )
+
+===============================================================================
+[doctest] test cases: 23 | 3 passed | 20 failed |
+[doctest] assertions: 84 | 38 passed | 46 failed |
[doctest] Status: FAILURE!
Program code.
diff --git a/examples/all_features/test_output/assertion_macros.cpp_junit.txt b/examples/all_features/test_output/assertion_macros.cpp_junit.txt
index d9a23f0..904841d 100644
--- a/examples/all_features/test_output/assertion_macros.cpp_junit.txt
+++ b/examples/all_features/test_output/assertion_macros.cpp_junit.txt
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
- <testsuite name="all_features" errors="0" failures="58" tests="80">
+ <testsuite name="all_features" errors="0" failures="60" tests="84">
<testcase classname="assertion_macros.cpp" name="normal macros" status="run">
<failure type="CHECK">
assertion_macros.cpp(0):
@@ -354,6 +354,20 @@
</failure>
</testcase>
+ <testcase classname="assertion_macros.cpp" name="nan" status="run">
+ <failure message="inf" type="CHECK_NAN">
+assertion_macros.cpp(0):
+CHECK_NAN( std::numeric_limits<long double>::infinity() ) is NOT correct!
+ values: CHECK_NAN( inf )
+
+ </failure>
+ <failure message="nanf" type="WARN_NOT_NAN">
+assertion_macros.cpp(0):
+WARN_NOT_NAN( std::numeric_limits<float>::quiet_NaN() ) is NOT correct!
+ values: WARN_NOT_NAN( nanf )
+
+ </failure>
+ </testcase>
</testsuite>
</testsuites>
Program code.
diff --git a/examples/all_features/test_output/assertion_macros.cpp_xml.txt b/examples/all_features/test_output/assertion_macros.cpp_xml.txt
index 7b64f0d..65245d9 100644
--- a/examples/all_features/test_output/assertion_macros.cpp_xml.txt
+++ b/examples/all_features/test_output/assertion_macros.cpp_xml.txt
@@ -580,8 +580,27 @@
</Expression>
<OverallResultsAsserts successes="0" failures="6" test_case_success="false"/>
</TestCase>
+ <TestCase name="nan" filename="assertion_macros.cpp" line="0">
+ <Expression success="false" type="CHECK_NAN" filename="assertion_macros.cpp" line="0">
+ <Original>
+ std::numeric_limits<long double>::infinity()
+ </Original>
+ <Expanded>
+ inf
+ </Expanded>
+ </Expression>
+ <Expression success="false" type="WARN_NOT_NAN" filename="assertion_macros.cpp" line="0">
+ <Original>
+ std::numeric_limits<float>::quiet_NaN()
+ </Original>
+ <Expanded>
+ nanf
+ </Expanded>
+ </Expression>
+ <OverallResultsAsserts successes="3" failures="1" test_case_success="false"/>
+ </TestCase>
</TestSuite>
- <OverallResultsAsserts successes="35" failures="45"/>
- <OverallResultsTestCases successes="3" failures="19"/>
+ <OverallResultsAsserts successes="38" failures="46"/>
+ <OverallResultsTestCases successes="3" failures="20"/>
</doctest>
Program code.
diff --git a/examples/all_features/test_output/asserts_used_outside_of_tests.cpp.txt b/examples/all_features/test_output/asserts_used_outside_of_tests.cpp.txt
index 9c09060..f2d8577 100644
--- a/examples/all_features/test_output/asserts_used_outside_of_tests.cpp.txt
+++ b/examples/all_features/test_output/asserts_used_outside_of_tests.cpp.txt
@@ -14,3 +14,5 @@
values: CHECK( false )
hello!
asserts_used_outside_of_tests.cpp(24): ERROR: an assert dealing with exceptions has failed!
+asserts_used_outside_of_tests.cpp(25): ERROR: CHECK_NAN( 0. ) is NOT correct!
+ values: CHECK_NAN( 0.0 )
diff --git a/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_junit.txt b/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_junit.txt
index 5727760..0b9472b 100644
--- a/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_junit.txt
+++ b/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_junit.txt
@@ -13,3 +13,5 @@
values: CHECK( false )
hello!
asserts_used_outside_of_tests.cpp(24): ERROR: an assert dealing with exceptions has failed!
+asserts_used_outside_of_tests.cpp(25): ERROR: CHECK_NAN( 0. ) is NOT correct!
+ values: CHECK_NAN( 0.0 )
diff --git a/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_xml.txt b/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_xml.txt
index 6490fb1..935fda8 100644
--- a/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_xml.txt
+++ b/examples/all_features/test_output/asserts_used_outside_of_tests.cpp_xml.txt
@@ -15,3 +15,5 @@
values: CHECK( false )
hello!
asserts_used_outside_of_tests.cpp(24): ERROR: an assert dealing with exceptions has failed!
+asserts_used_outside_of_tests.cpp(25): ERROR: CHECK_NAN( 0. ) is NOT correct!
+ values: CHECK_NAN( 0.0 )
diff --git a/examples/all_features/test_output/filter_2.txt b/examples/all_features/test_output/filter_2.txt
index d4b108f..662a7e2 100644
--- a/examples/all_features/test_output/filter_2.txt
+++ b/examples/all_features/test_output/filter_2.txt
@@ -1,6 +1,6 @@
[doctest] run with "--help" for options
===============================================================================
-[doctest] test cases: 0 | 0 passed | 0 failed | 95 skipped
+[doctest] test cases: 0 | 0 passed | 0 failed | 96 skipped
[doctest] assertions: 0 | 0 passed | 0 failed |
[doctest] Status: SUCCESS!
Program code.
diff --git a/examples/all_features/test_output/filter_2_xml.txt b/examples/all_features/test_output/filter_2_xml.txt
index 90a2824..57a62a0 100644
--- a/examples/all_features/test_output/filter_2_xml.txt
+++ b/examples/all_features/test_output/filter_2_xml.txt
@@ -88,6 +88,7 @@
<TestCase name="namespace 7 member vs global" filename="namespace7.cpp" line="0" skipped="true"/>
<TestCase name="namespace 8 friend vs global" filename="namespace8.cpp" line="0" skipped="true"/>
<TestCase name="namespace 9 both global" filename="namespace9.cpp" line="0" skipped="true"/>
+ <TestCase name="nan" filename="assertion_macros.cpp" line="0" skipped="true"/>
<TestCase name="no checks" filename="no_failures.cpp" line="0" skipped="true"/>
<TestCase name="normal macros" filename="assertion_macros.cpp" line="0" skipped="true"/>
</TestSuite>
@@ -135,6 +136,6 @@
<TestCase name="will end from an unknown exception" filename="coverage_maxout.cpp" line="0" skipped="true"/>
</TestSuite>
<OverallResultsAsserts successes="0" failures="0"/>
- <OverallResultsTestCases successes="0" failures="0" skipped="95"/>
+ <OverallResultsTestCases successes="0" failures="0" skipped="96"/>
</doctest>
Program code.
diff --git a/scripts/coverage_maxout.cpp b/scripts/coverage_maxout.cpp
index ed3b4fb..8b6e574 100644
--- a/scripts/coverage_maxout.cpp
+++ b/scripts/coverage_maxout.cpp
@@ -15,6 +15,7 @@
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
#include <ostream>
#include <sstream>
+#include <stdexcept>
DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
#ifndef DOCTEST_CONFIG_DISABLE
@@ -94,7 +95,11 @@
// trigger code path for String to ostream through operator<<
oss << str;
// trigger code path for assert string of a non-existent assert type
- oss << assertString(static_cast<assertType::Enum>(3));
+#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
+ try {
+ assertString(static_cast<assertType::Enum>(3));
+ } catch (const std::logic_error&) { }
+#endif
str += oss.str().c_str();
str += failureString(assertType::is_normal);
CHECK(str == "omgomgomgaaaNULLtrue00.5f0.50.199991111111true0.50.50.1cc"