From 6fbb3f07235311d0c228b17b52406343a7aed8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 26 Feb 2023 00:14:32 +0100 Subject: [PATCH] Add IsNaN matcher --- docs/matchers.md | 13 ++++++-- .../catch_matchers_floating_point.cpp | 14 ++++++++- .../catch_matchers_floating_point.hpp | 30 ++++++++++++++----- .../Baselines/compact.sw.approved.txt | 4 ++- .../Baselines/compact.sw.multi.approved.txt | 4 ++- .../Baselines/console.std.approved.txt | 2 +- .../Baselines/console.sw.approved.txt | 26 +++++++++++++++- .../Baselines/console.sw.multi.approved.txt | 26 +++++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 4 ++- .../Baselines/junit.sw.multi.approved.txt | 4 ++- .../Baselines/sonarqube.sw.approved.txt | 2 ++ .../Baselines/sonarqube.sw.multi.approved.txt | 2 ++ tests/SelfTest/Baselines/tap.sw.approved.txt | 6 +++- .../Baselines/tap.sw.multi.approved.txt | 6 +++- tests/SelfTest/Baselines/xml.sw.approved.txt | 24 ++++++++++++++- .../Baselines/xml.sw.multi.approved.txt | 24 ++++++++++++++- tests/SelfTest/UsageTests/Matchers.tests.cpp | 8 +++++ 17 files changed, 178 insertions(+), 21 deletions(-) diff --git a/docs/matchers.md b/docs/matchers.md index dda49110..d2a2f3c1 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -141,18 +141,27 @@ are a permutation of the ones in `some_vec`. ### Floating point matchers -Catch2 provides 3 matchers that target floating point numbers. These +Catch2 provides 4 matchers that target floating point numbers. These are: * `WithinAbs(double target, double margin)`, * `WithinULP(FloatingPoint target, uint64_t maxUlpDiff)`, and * `WithinRel(FloatingPoint target, FloatingPoint eps)`. +* `IsNaN()` > `WithinRel` matcher was introduced in Catch2 2.10.0 -For more details, read [the docs on comparing floating point +> `IsNaN` matcher was introduced in Catch2 X.Y.Z. + +The first three serve to compare two floating pointe numbers. For more +details about how they work, read [the docs on comparing floating point numbers](comparing-floating-point-numbers.md#floating-point-matchers). +`IsNaN` then does exactly what it says on the tin. It matches the input +if it is a NaN (Not a Number). The advantage of using it over just plain +`REQUIRE(std::isnan(x))`, is that if the check fails, with `REQUIRE` you +won't see the value of `x`, but with `REQUIRE_THAT(x, IsNaN())`, you will. + ### Miscellaneous matchers diff --git a/src/catch2/matchers/catch_matchers_floating_point.cpp b/src/catch2/matchers/catch_matchers_floating_point.cpp index 719fb51f..6e596466 100644 --- a/src/catch2/matchers/catch_matchers_floating_point.cpp +++ b/src/catch2/matchers/catch_matchers_floating_point.cpp @@ -225,5 +225,17 @@ WithinRelMatcher WithinRel(float target) { } -} // namespace Matchers + +bool IsNaNMatcher::match( double const& matchee ) const { + return std::isnan( matchee ); +} + +std::string IsNaNMatcher::describe() const { + using namespace std::string_literals; + return "is NaN"s; +} + +IsNaNMatcher IsNaN() { return IsNaNMatcher(); } + + } // namespace Matchers } // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_floating_point.hpp b/src/catch2/matchers/catch_matchers_floating_point.hpp index abdaf159..76816633 100644 --- a/src/catch2/matchers/catch_matchers_floating_point.hpp +++ b/src/catch2/matchers/catch_matchers_floating_point.hpp @@ -27,6 +27,11 @@ namespace Matchers { double m_margin; }; + //! Creates a matcher that accepts numbers within certain range of target + WithinAbsMatcher WithinAbs( double target, double margin ); + + + class WithinUlpsMatcher final : public MatcherBase { public: WithinUlpsMatcher( double target, @@ -40,6 +45,13 @@ namespace Matchers { Detail::FloatingPointKind m_type; }; + //! Creates a matcher that accepts doubles within certain ULP range of target + WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); + //! Creates a matcher that accepts floats within certain ULP range of target + WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); + + + // Given IEEE-754 format for floats and doubles, we can assume // that float -> double promotion is lossless. Given this, we can // assume that if we do the standard relative comparison of @@ -56,13 +68,6 @@ namespace Matchers { double m_epsilon; }; - //! Creates a matcher that accepts doubles within certain ULP range of target - WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); - //! Creates a matcher that accepts floats within certain ULP range of target - WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); - //! Creates a matcher that accepts numbers within certain range of target - WithinAbsMatcher WithinAbs(double target, double margin); - //! Creates a matcher that accepts doubles within certain relative range of target WithinRelMatcher WithinRel(double target, double eps); //! Creates a matcher that accepts doubles within 100*DBL_EPS relative range of target @@ -72,6 +77,17 @@ namespace Matchers { //! Creates a matcher that accepts floats within 100*FLT_EPS relative range of target WithinRelMatcher WithinRel(float target); + + + class IsNaNMatcher final : public MatcherBase { + public: + IsNaNMatcher() = default; + bool match( double const& matchee ) const override; + std::string describe() const override; + }; + + IsNaNMatcher IsNaN(); + } // namespace Matchers } // namespace Catch diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index 1f1622c7..541770cc 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -629,6 +629,7 @@ Matchers.tests.cpp:: passed: WithinULP( 1., 0 ) Matchers.tests.cpp:: passed: WithinRel( 1., 0. ) Matchers.tests.cpp:: passed: WithinRel( 1., -0.2 ), std::domain_error Matchers.tests.cpp:: passed: WithinRel( 1., 1. ), std::domain_error +Matchers.tests.cpp:: passed: 1., !IsNaN() for: 1.0 not is NaN Matchers.tests.cpp:: passed: 10.f, WithinRel( 11.1f, 0.1f ) for: 10.0f and 11.1 are within 10% of each other Matchers.tests.cpp:: passed: 10.f, !WithinRel( 11.2f, 0.1f ) for: 10.0f not and 11.2 are within 10% of each other Matchers.tests.cpp:: passed: 1.f, !WithinRel( 0.f, 0.99f ) for: 1.0f not and 0 are within 99% of each other @@ -661,6 +662,7 @@ Matchers.tests.cpp:: passed: WithinULP( 1.f, static_cast( Matchers.tests.cpp:: passed: WithinRel( 1.f, 0.f ) Matchers.tests.cpp:: passed: WithinRel( 1.f, -0.2f ), std::domain_error Matchers.tests.cpp:: passed: WithinRel( 1.f, 1.f ), std::domain_error +Matchers.tests.cpp:: passed: 1., !IsNaN() for: 1.0 not is NaN Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 @@ -2537,6 +2539,6 @@ InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: test cases: 409 | 309 passed | 84 failed | 5 skipped | 11 failed as expected -assertions: 2224 | 2047 passed | 145 failed | 32 failed as expected +assertions: 2226 | 2049 passed | 145 failed | 32 failed as expected diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index 4cfcb1ab..5b292da1 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -627,6 +627,7 @@ Matchers.tests.cpp:: passed: WithinULP( 1., 0 ) Matchers.tests.cpp:: passed: WithinRel( 1., 0. ) Matchers.tests.cpp:: passed: WithinRel( 1., -0.2 ), std::domain_error Matchers.tests.cpp:: passed: WithinRel( 1., 1. ), std::domain_error +Matchers.tests.cpp:: passed: 1., !IsNaN() for: 1.0 not is NaN Matchers.tests.cpp:: passed: 10.f, WithinRel( 11.1f, 0.1f ) for: 10.0f and 11.1 are within 10% of each other Matchers.tests.cpp:: passed: 10.f, !WithinRel( 11.2f, 0.1f ) for: 10.0f not and 11.2 are within 10% of each other Matchers.tests.cpp:: passed: 1.f, !WithinRel( 0.f, 0.99f ) for: 1.0f not and 0 are within 99% of each other @@ -659,6 +660,7 @@ Matchers.tests.cpp:: passed: WithinULP( 1.f, static_cast( Matchers.tests.cpp:: passed: WithinRel( 1.f, 0.f ) Matchers.tests.cpp:: passed: WithinRel( 1.f, -0.2f ), std::domain_error Matchers.tests.cpp:: passed: WithinRel( 1.f, 1.f ), std::domain_error +Matchers.tests.cpp:: passed: 1., !IsNaN() for: 1.0 not is NaN Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 Generators.tests.cpp:: passed: i % 2 == 0 for: 0 == 0 @@ -2526,6 +2528,6 @@ InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: test cases: 409 | 309 passed | 84 failed | 5 skipped | 11 failed as expected -assertions: 2224 | 2047 passed | 145 failed | 32 failed as expected +assertions: 2226 | 2049 passed | 145 failed | 32 failed as expected diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 46df088e..15d8b024 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1534,5 +1534,5 @@ due to unexpected exception with message: =============================================================================== test cases: 409 | 323 passed | 69 failed | 6 skipped | 11 failed as expected -assertions: 2207 | 2047 passed | 128 failed | 32 failed as expected +assertions: 2209 | 2049 passed | 128 failed | 32 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index fcd9626f..26d5e8f3 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -4671,6 +4671,18 @@ Matchers.tests.cpp:: PASSED: Matchers.tests.cpp:: PASSED: REQUIRE_THROWS_AS( WithinRel( 1., 1. ), std::domain_error ) +------------------------------------------------------------------------------- +Floating point matchers: double + IsNaN +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1., !IsNaN() ) +with expansion: + 1.0 not is NaN + ------------------------------------------------------------------------------- Floating point matchers: float Relative @@ -4864,6 +4876,18 @@ Matchers.tests.cpp:: PASSED: Matchers.tests.cpp:: PASSED: REQUIRE_THROWS_AS( WithinRel( 1.f, 1.f ), std::domain_error ) +------------------------------------------------------------------------------- +Floating point matchers: float + IsNaN +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1., !IsNaN() ) +with expansion: + 1.0 not is NaN + ------------------------------------------------------------------------------- Generators -- adapters Filtering by predicate @@ -18208,5 +18232,5 @@ Misc.tests.cpp:: PASSED: =============================================================================== test cases: 409 | 309 passed | 84 failed | 5 skipped | 11 failed as expected -assertions: 2224 | 2047 passed | 145 failed | 32 failed as expected +assertions: 2226 | 2049 passed | 145 failed | 32 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index 1e9c0677..80b63ab8 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -4669,6 +4669,18 @@ Matchers.tests.cpp:: PASSED: Matchers.tests.cpp:: PASSED: REQUIRE_THROWS_AS( WithinRel( 1., 1. ), std::domain_error ) +------------------------------------------------------------------------------- +Floating point matchers: double + IsNaN +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1., !IsNaN() ) +with expansion: + 1.0 not is NaN + ------------------------------------------------------------------------------- Floating point matchers: float Relative @@ -4862,6 +4874,18 @@ Matchers.tests.cpp:: PASSED: Matchers.tests.cpp:: PASSED: REQUIRE_THROWS_AS( WithinRel( 1.f, 1.f ), std::domain_error ) +------------------------------------------------------------------------------- +Floating point matchers: float + IsNaN +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1., !IsNaN() ) +with expansion: + 1.0 not is NaN + ------------------------------------------------------------------------------- Generators -- adapters Filtering by predicate @@ -18197,5 +18221,5 @@ Misc.tests.cpp:: PASSED: =============================================================================== test cases: 409 | 309 passed | 84 failed | 5 skipped | 11 failed as expected -assertions: 2224 | 2047 passed | 145 failed | 32 failed as expected +assertions: 2226 | 2049 passed | 145 failed | 32 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 58762161..25129349 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -694,12 +694,14 @@ at Message.tests.cpp: + + diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt index 1da7a257..6220d8e2 100644 --- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt @@ -1,6 +1,6 @@ - + @@ -693,12 +693,14 @@ at Message.tests.cpp: + + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index 4d1dcf2f..a4e08bd2 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -1172,12 +1172,14 @@ at Matchers.tests.cpp: + + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt index 84ca07d6..c00defae 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt @@ -1171,12 +1171,14 @@ at Matchers.tests.cpp: + + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 16728b46..920c95fd 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -1190,6 +1190,8 @@ ok {test-number} - WithinRel( 1., 0. ) ok {test-number} - WithinRel( 1., -0.2 ), std::domain_error # Floating point matchers: double ok {test-number} - WithinRel( 1., 1. ), std::domain_error +# Floating point matchers: double +ok {test-number} - 1., !IsNaN() for: 1.0 not is NaN # Floating point matchers: float ok {test-number} - 10.f, WithinRel( 11.1f, 0.1f ) for: 10.0f and 11.1 are within 10% of each other # Floating point matchers: float @@ -1254,6 +1256,8 @@ ok {test-number} - WithinRel( 1.f, 0.f ) ok {test-number} - WithinRel( 1.f, -0.2f ), std::domain_error # Floating point matchers: float ok {test-number} - WithinRel( 1.f, 1.f ), std::domain_error +# Floating point matchers: float +ok {test-number} - 1., !IsNaN() for: 1.0 not is NaN # Generators -- adapters ok {test-number} - i % 2 == 0 for: 0 == 0 # Generators -- adapters @@ -4473,5 +4477,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2235 +1..2237 diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index 44d078f7..c0e0c4db 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -1188,6 +1188,8 @@ ok {test-number} - WithinRel( 1., 0. ) ok {test-number} - WithinRel( 1., -0.2 ), std::domain_error # Floating point matchers: double ok {test-number} - WithinRel( 1., 1. ), std::domain_error +# Floating point matchers: double +ok {test-number} - 1., !IsNaN() for: 1.0 not is NaN # Floating point matchers: float ok {test-number} - 10.f, WithinRel( 11.1f, 0.1f ) for: 10.0f and 11.1 are within 10% of each other # Floating point matchers: float @@ -1252,6 +1254,8 @@ ok {test-number} - WithinRel( 1.f, 0.f ) ok {test-number} - WithinRel( 1.f, -0.2f ), std::domain_error # Floating point matchers: float ok {test-number} - WithinRel( 1.f, 1.f ), std::domain_error +# Floating point matchers: float +ok {test-number} - 1., !IsNaN() for: 1.0 not is NaN # Generators -- adapters ok {test-number} - i % 2 == 0 for: 0 == 0 # Generators -- adapters @@ -4462,5 +4466,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2235 +1..2237 diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 97aa7d85..6a0d1587 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -5287,6 +5287,17 @@ C +
+ + + 1., !IsNaN() + + + 1.0 not is NaN + + + +
@@ -5564,6 +5575,17 @@ C +
+ + + 1., !IsNaN() + + + 1.0 not is NaN + + + +
@@ -21181,6 +21203,6 @@ b1! - + diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index aed70cfa..c6ddfc80 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -5287,6 +5287,17 @@ C +
+ + + 1., !IsNaN() + + + 1.0 not is NaN + + + +
@@ -5564,6 +5575,17 @@ C +
+ + + 1., !IsNaN() + + + 1.0 not is NaN + + + +
@@ -21180,6 +21202,6 @@ b1! - + diff --git a/tests/SelfTest/UsageTests/Matchers.tests.cpp b/tests/SelfTest/UsageTests/Matchers.tests.cpp index b2239572..49e25232 100644 --- a/tests/SelfTest/UsageTests/Matchers.tests.cpp +++ b/tests/SelfTest/UsageTests/Matchers.tests.cpp @@ -497,6 +497,9 @@ TEST_CASE( "Floating point matchers: float", "[matchers][floating-point]" ) { REQUIRE_THROWS_AS( WithinRel( 1.f, -0.2f ), std::domain_error ); REQUIRE_THROWS_AS( WithinRel( 1.f, 1.f ), std::domain_error ); } + SECTION( "IsNaN" ) { + REQUIRE_THAT( 1., !IsNaN() ); + } } TEST_CASE( "Floating point matchers: double", "[matchers][floating-point]" ) { @@ -552,6 +555,9 @@ TEST_CASE( "Floating point matchers: double", "[matchers][floating-point]" ) { REQUIRE_THROWS_AS( WithinRel( 1., -0.2 ), std::domain_error ); REQUIRE_THROWS_AS( WithinRel( 1., 1. ), std::domain_error ); } + SECTION("IsNaN") { + REQUIRE_THAT( 1., !IsNaN() ); + } } TEST_CASE( "Floating point matchers that are problematic in approvals", @@ -566,6 +572,8 @@ TEST_CASE( "Floating point matchers that are problematic in approvals", REQUIRE_THAT( NAN, !WithinRel( NAN ) ); REQUIRE_THAT( 1., !WithinRel( NAN ) ); REQUIRE_THAT( NAN, !WithinRel( 1. ) ); + REQUIRE_THAT( NAN, IsNaN() ); + REQUIRE_THAT( static_cast(NAN), IsNaN() ); } TEST_CASE( "Arbitrary predicate matcher", "[matchers][generic]" ) {