From 651247c7f40eda6846fec736d2f9edac8949dacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Sun, 10 May 2026 23:05:46 +0200 Subject: [PATCH] Support for constexpr matchers in C++20 (P0784) To make this all work, I had to remove the stringification cache from matchers. In theory, this can cause performance penalty in cases where single matcher instance is stringified multiple times, but in practice this does not happen much, and the difference is surprisingly small anyway, because the performance of stringification is already horrible and full of allocating strings just to throw them away. The matcher combinators need P2738 from C++26 to be `constexpr`. Closes #3091 --- docs/matchers.md | 21 ++++ docs/other-macros.md | 24 +++++ .../internal/catch_compiler_capabilities.hpp | 12 +++ src/catch2/matchers/catch_matchers.cpp | 10 +- src/catch2/matchers/catch_matchers.hpp | 44 +++++++-- .../matchers/catch_matchers_templated.cpp | 6 +- .../matchers/catch_matchers_templated.hpp | 99 ++++++++++++------- tests/BUILD.bazel | 1 + tests/CMakeLists.txt | 3 +- tests/ExtraTests/CMakeLists.txt | 2 +- tests/ExtraTests/X02-DisabledMacros.cpp | 2 + tests/ExtraTests/X05-DeferredStaticChecks.cpp | 26 +++++ .../UsageTests/MatchersConstexpr.tests.cpp | 52 ++++++++++ tests/meson.build | 1 + 14 files changed, 254 insertions(+), 49 deletions(-) create mode 100644 tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp diff --git a/docs/matchers.md b/docs/matchers.md index 4b9445ae..a013245c 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -6,6 +6,7 @@ [Built-in matchers](#built-in-matchers)
[Writing custom matchers (old style)](#writing-custom-matchers-old-style)
[Writing custom matchers (new style)](#writing-custom-matchers-new-style)
+[Constexpr matchers](#constexpr-matchers)
Matchers, as popularized by the [Hamcrest](https://en.wikipedia.org/wiki/Hamcrest) framework are an alternative way to write assertions, useful for tests @@ -471,6 +472,26 @@ and new style matchers arbitrarily. > `MatcherGenericBase` lives in `catch2/matchers/catch_matchers_templated.hpp` +## Constexpr matchers + +> Support for constexpr matchers was introduced in Catch2 vX.Y.Z + +When compiled for C++20, the new-style matchers (can) support `constexpr` +matching, albeit not `constexpr` stringification. The matcher combinators +require C++26 (or at least P2738) to be `constexpr` compatible. + +This can be used together with the `STATIC_REQUIRE_THAT` macro to write +matcher-based static assertions like this: + +```cpp +TEST_CASE("Constexpr support for matchers", "[constexpr][matchers]") { + STATIC_REQUIRE_THAT( 1, MatchAll() ); + STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() ); + STATIC_REQUIRE_THAT( 1, !!MatchAll() ); +} +``` + + --- [Home](Readme.md#top) diff --git a/docs/other-macros.md b/docs/other-macros.md index 79990a6a..de7e4bc6 100644 --- a/docs/other-macros.md +++ b/docs/other-macros.md @@ -91,6 +91,30 @@ TEST_CASE("STATIC_CHECK showcase", "[traits]") { } ``` +* `STATIC_REQUIRE_THAT` and `STATIC_CHECK_THAT` + +> `STATIC_REQUIRE_THAT` and `STATIC_CHECK_THAT` was introduced in Catch2 X.Y.Z + +`STATIC_{REQUIRE,CHECK}_THAT` are analogous to `STATIC_{REQUIRE,CHECK}`, +but for matchers. They are always defined, even if the current compiler +does not support `constexpr` matchers, but in that case the compilation +will always fail. + +Just like `STATIC_{REQUIRE,CHECK}`, `STATIC_{REQUIRE,CHECK}_THAT` can be +delayed into runtime through the `CATCH_CONFIG_RUNTIME_STATIC_REQUIRE` +configuration option. + +Example: +```cpp +TEST_CASE("Constexpr support for matchers", "[constexpr][matchers]") { + STATIC_REQUIRE_THAT( 1, MatchAll() ); + STATIC_REQUIRE_THAT( 1, MatchAll() && MatchAll() ); + STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() ); + STATIC_REQUIRE_THAT( 1, !!MatchAll() ); +} +``` + + ## Test case related macros * `REGISTER_TEST_CASE` diff --git a/src/catch2/internal/catch_compiler_capabilities.hpp b/src/catch2/internal/catch_compiler_capabilities.hpp index d0c79243..2d6d0c75 100644 --- a/src/catch2/internal/catch_compiler_capabilities.hpp +++ b/src/catch2/internal/catch_compiler_capabilities.hpp @@ -35,6 +35,18 @@ # define CATCH_CPP20_OR_GREATER #endif +// Matchers are only constexpr-able in C++20 +#if defined( CATCH_CPP20_OR_GREATER ) && \ + defined( __cpp_constexpr_dynamic_alloc ) && \ + __cpp_constexpr_dynamic_alloc >= 201907L && \ + /* GCC < 13 define the feature macro, but compiler bugs stop us from using it */ \ + ( !defined( __GNUC__ ) || __GNUC__ >= 13 || defined(__clang__) ) +# define CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED +# define CATCH_DESTRUCTOR_CONSTEXPR constexpr +#else +# define CATCH_DESTRUCTOR_CONSTEXPR +#endif + // Only GCC compiler should be used in this block, so other compilers trying to // mask themselves as GCC should be ignored. #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER) diff --git a/src/catch2/matchers/catch_matchers.cpp b/src/catch2/matchers/catch_matchers.cpp index 123b3041..d1add581 100644 --- a/src/catch2/matchers/catch_matchers.cpp +++ b/src/catch2/matchers/catch_matchers.cpp @@ -13,13 +13,13 @@ namespace Catch { namespace Matchers { std::string MatcherUntypedBase::toString() const { - if (m_cachedToString.empty()) { - m_cachedToString = describe(); - } - return m_cachedToString; + return describe(); } - MatcherUntypedBase::~MatcherUntypedBase() = default; + std::string MatcherUntypedBase::describe() const { + using namespace std::string_literals; + return "Undescribed matcher"s; + } } // namespace Matchers } // namespace Catch diff --git a/src/catch2/matchers/catch_matchers.hpp b/src/catch2/matchers/catch_matchers.hpp index 90ed3338..6d30c7f0 100644 --- a/src/catch2/matchers/catch_matchers.hpp +++ b/src/catch2/matchers/catch_matchers.hpp @@ -20,10 +20,10 @@ namespace Matchers { class MatcherUntypedBase { public: - MatcherUntypedBase() = default; + constexpr MatcherUntypedBase() = default; - MatcherUntypedBase(MatcherUntypedBase const&) = default; - MatcherUntypedBase(MatcherUntypedBase&&) = default; + constexpr MatcherUntypedBase(MatcherUntypedBase const&) = default; + constexpr MatcherUntypedBase(MatcherUntypedBase&&) = default; MatcherUntypedBase& operator = (MatcherUntypedBase const&) = delete; MatcherUntypedBase& operator = (MatcherUntypedBase&&) = delete; @@ -31,9 +31,9 @@ namespace Matchers { std::string toString() const; protected: - virtual ~MatcherUntypedBase(); // = default; - virtual std::string describe() const = 0; - mutable std::string m_cachedToString; + CATCH_DESTRUCTOR_CONSTEXPR virtual ~MatcherUntypedBase() = default; + //! Should be overridden, but we provide default "undescribed" impl + virtual std::string describe() const; }; @@ -215,6 +215,19 @@ namespace Matchers { #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) \ + static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \ + CATCH_SUCCEED( #matcher ".match( " #arg " )" ) + #define CATCH_STATIC_CHECK_THAT( arg, matcher ) \ + static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \ + CATCH_SUCCEED( #matcher ".match( " #arg " )" ) + #else + #define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) CATCH_REQUIRE_THAT( arg, matcher ) + #define CATCH_STATIC_CHECK_THAT( arg, matcher ) CATCH_CHECK_THAT( arg, matcher ) + #endif + + #elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) @@ -226,6 +239,9 @@ namespace Matchers { #define CATCH_CHECK_THAT( arg, matcher ) (void)(0) #define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) + #define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) (void)(0) + #define CATCH_STATIC_CHECK_THAT( arg, matcher ) (void)(0) + #elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) @@ -237,6 +253,19 @@ namespace Matchers { #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define STATIC_REQUIRE_THAT( arg, matcher ) \ + static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \ + SUCCEED( #matcher ".match( " #arg " )" ) + #define STATIC_CHECK_THAT( arg, matcher ) \ + static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \ + SUCCEED( #matcher ".match( " #arg " )" ) + #else + #define STATIC_REQUIRE_THAT( arg, matcher ) REQUIRE_THAT( arg, matcher ) + #define STATIC_CHECK_THAT( arg, matcher ) CHECK_THAT( arg, matcher ) + #endif + + #elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) #define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) @@ -248,6 +277,9 @@ namespace Matchers { #define CHECK_THAT( arg, matcher ) (void)(0) #define REQUIRE_THAT( arg, matcher ) (void)(0) + #define STATIC_REQUIRE_THAT( arg, matcher ) (void)(0) + #define STATIC_CHECK_THAT( arg, matcher ) (void)(0) + #endif // end of user facing macro declarations #endif // CATCH_MATCHERS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_templated.cpp b/src/catch2/matchers/catch_matchers_templated.cpp index 2fc529d2..e755b479 100644 --- a/src/catch2/matchers/catch_matchers_templated.cpp +++ b/src/catch2/matchers/catch_matchers_templated.cpp @@ -9,7 +9,11 @@ namespace Catch { namespace Matchers { - MatcherGenericBase::~MatcherGenericBase() = default; + + std::string MatcherGenericBase::describe() const { + using namespace std::string_literals; + return "Undescribed generic matcher"s; + } namespace Detail { diff --git a/src/catch2/matchers/catch_matchers_templated.hpp b/src/catch2/matchers/catch_matchers_templated.hpp index 0cd40163..2c6247f8 100644 --- a/src/catch2/matchers/catch_matchers_templated.hpp +++ b/src/catch2/matchers/catch_matchers_templated.hpp @@ -22,12 +22,12 @@ namespace Catch { namespace Matchers { class MatcherGenericBase : public MatcherUntypedBase { + std::string describe() const override; public: - MatcherGenericBase() = default; - ~MatcherGenericBase() override; // = default; + constexpr MatcherGenericBase() = default; - MatcherGenericBase(MatcherGenericBase const&) = default; - MatcherGenericBase(MatcherGenericBase&&) = default; + constexpr MatcherGenericBase(MatcherGenericBase const&) = default; + constexpr MatcherGenericBase(MatcherGenericBase&&) = default; MatcherGenericBase& operator=(MatcherGenericBase const&) = delete; MatcherGenericBase& operator=(MatcherGenericBase&&) = delete; @@ -36,7 +36,9 @@ namespace Matchers { namespace Detail { template - std::array array_cat(std::array && lhs, std::array && rhs) { + constexpr std::array + array_cat( std::array&& lhs, + std::array&& rhs ) { std::array arr{}; std::copy_n(lhs.begin(), N, arr.begin()); std::copy_n(rhs.begin(), M, arr.begin() + N); @@ -44,7 +46,8 @@ namespace Matchers { } template - std::array array_cat(std::array && lhs, void const* rhs) { + constexpr std::array + array_cat( std::array&& lhs, void const* rhs ) { std::array arr{}; std::copy_n(lhs.begin(), N, arr.begin()); arr[N] = rhs; @@ -52,7 +55,8 @@ namespace Matchers { } template - std::array array_cat(void const* lhs, std::array && rhs) { + constexpr std::array + array_cat( void const* lhs, std::array&& rhs ) { std::array arr{ {lhs} }; std::copy_n(rhs.begin(), N, arr.begin() + 1); return arr; @@ -75,23 +79,31 @@ namespace Matchers { template - bool match_all_of(Arg&&, std::array const&, std::index_sequence<>) { + constexpr bool match_all_of( Arg&&, + std::array const&, + std::index_sequence<> ) { return true; } template - bool match_all_of(Arg&& arg, std::array const& matchers, std::index_sequence) { + constexpr bool match_all_of( Arg&& arg, + std::array const& matchers, + std::index_sequence ) { return static_cast(matchers[Idx])->match(arg) && match_all_of(arg, matchers, std::index_sequence{}); } template - bool match_any_of(Arg&&, std::array const&, std::index_sequence<>) { + constexpr bool match_any_of( Arg&&, + std::array const&, + std::index_sequence<> ) { return false; } template - bool match_any_of(Arg&& arg, std::array const& matchers, std::index_sequence) { + constexpr bool match_any_of( Arg&& arg, + std::array const& matchers, + std::index_sequence ) { return static_cast(matchers[Idx])->match(arg) || match_any_of(arg, matchers, std::index_sequence{}); } @@ -112,15 +124,18 @@ namespace Matchers { public: MatchAllOfGeneric(MatchAllOfGeneric const&) = delete; MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete; - MatchAllOfGeneric(MatchAllOfGeneric&&) = default; - MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default; + constexpr MatchAllOfGeneric( MatchAllOfGeneric&& ) = default; + constexpr MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default; - MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND) + constexpr MatchAllOfGeneric( + MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND ) : m_matchers{ {std::addressof(matchers)...} } {} - explicit MatchAllOfGeneric(std::array matchers) : m_matchers{matchers} {} + constexpr explicit MatchAllOfGeneric( + std::array matchers ): + m_matchers{ matchers } {} template - bool match(Arg&& arg) const { + constexpr bool match( Arg&& arg ) const { return match_all_of(arg, m_matchers, std::index_sequence_for{}); } @@ -136,7 +151,7 @@ namespace Matchers { //! Avoids type nesting for `GenericAllOf && GenericAllOf` case template - friend + constexpr friend MatchAllOfGeneric operator && ( MatchAllOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, MatchAllOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) { @@ -145,7 +160,8 @@ namespace Matchers { //! Avoids type nesting for `GenericAllOf && some matcher` case template - friend std::enable_if_t, + constexpr friend std::enable_if_t< + is_matcher_v, MatchAllOfGeneric> operator && ( MatchAllOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { @@ -154,7 +170,8 @@ namespace Matchers { //! Avoids type nesting for `some matcher && GenericAllOf` case template - friend std::enable_if_t, + constexpr friend std::enable_if_t< + is_matcher_v, MatchAllOfGeneric> operator && ( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, MatchAllOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) { @@ -168,15 +185,18 @@ namespace Matchers { public: MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete; MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete; - MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default; - MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default; + constexpr MatchAnyOfGeneric( MatchAnyOfGeneric&& ) = default; + constexpr MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default; - MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND) + constexpr MatchAnyOfGeneric( + MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND ) : m_matchers{ {std::addressof(matchers)...} } {} - explicit MatchAnyOfGeneric(std::array matchers) : m_matchers{matchers} {} + constexpr explicit MatchAnyOfGeneric( + std::array matchers ): + m_matchers{ matchers } {} template - bool match(Arg&& arg) const { + constexpr bool match( Arg&& arg ) const { return match_any_of(arg, m_matchers, std::index_sequence_for{}); } @@ -192,7 +212,8 @@ namespace Matchers { //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case template - friend MatchAnyOfGeneric operator || ( + constexpr friend MatchAnyOfGeneric + operator||( MatchAnyOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, MatchAnyOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND ) { return MatchAnyOfGeneric{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; @@ -200,7 +221,8 @@ namespace Matchers { //! Avoids type nesting for `GenericAnyOf || some matcher` case template - friend std::enable_if_t, + constexpr friend std::enable_if_t< + is_matcher_v, MatchAnyOfGeneric> operator || ( MatchAnyOfGeneric&& lhs CATCH_ATTR_LIFETIMEBOUND, MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { @@ -209,7 +231,8 @@ namespace Matchers { //! Avoids type nesting for `some matcher || GenericAnyOf` case template - friend std::enable_if_t, + constexpr friend std::enable_if_t< + is_matcher_v, MatchAnyOfGeneric> operator || ( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, MatchAnyOfGeneric&& rhs CATCH_ATTR_LIFETIMEBOUND) { @@ -225,14 +248,15 @@ namespace Matchers { public: MatchNotOfGeneric(MatchNotOfGeneric const&) = delete; MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete; - MatchNotOfGeneric(MatchNotOfGeneric&&) = default; - MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default; + constexpr MatchNotOfGeneric( MatchNotOfGeneric&& ) = default; + constexpr MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default; - explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND) + constexpr explicit MatchNotOfGeneric( + MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) : m_matcher{matcher} {} template - bool match(Arg&& arg) const { + constexpr bool match( Arg&& arg ) const { return !m_matcher.match(arg); } @@ -241,7 +265,7 @@ namespace Matchers { } //! Negating negation can just unwrap and return underlying matcher - friend MatcherT const& + constexpr friend MatcherT const& operator!( MatchNotOfGeneric const& matcher CATCH_ATTR_LIFETIMEBOUND ) { return matcher.m_matcher; @@ -252,14 +276,18 @@ namespace Matchers { // compose only generic matchers template - std::enable_if_t, Detail::MatchAllOfGeneric> + constexpr std::enable_if_t< + Detail::are_generic_matchers_v, + Detail::MatchAllOfGeneric> operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; } template - std::enable_if_t, Detail::MatchAnyOfGeneric> + constexpr std::enable_if_t< + Detail::are_generic_matchers_v, + Detail::MatchAnyOfGeneric> operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND, MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) { return { lhs, rhs }; @@ -267,7 +295,8 @@ namespace Matchers { //! Wrap provided generic matcher in generic negator template - std::enable_if_t, Detail::MatchNotOfGeneric> + constexpr std::enable_if_t, + Detail::MatchNotOfGeneric> operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) { return Detail::MatchNotOfGeneric{matcher}; } diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel index 58c05205..1e192a1c 100644 --- a/tests/BUILD.bazel +++ b/tests/BUILD.bazel @@ -62,6 +62,7 @@ cc_test( "SelfTest/UsageTests/Exception.tests.cpp", "SelfTest/UsageTests/Generators.tests.cpp", "SelfTest/UsageTests/Matchers.tests.cpp", + "SelfTest/UsageTests/MatchersConstexpr.tests.cpp", "SelfTest/UsageTests/MatchersRanges.tests.cpp", "SelfTest/UsageTests/Message.tests.cpp", "SelfTest/UsageTests/Misc.tests.cpp", diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3329e90f..7429734f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -124,8 +124,9 @@ set(TEST_SOURCES ${SELF_TEST_DIR}/UsageTests/ToStringWhich.tests.cpp ${SELF_TEST_DIR}/UsageTests/Tricky.tests.cpp ${SELF_TEST_DIR}/UsageTests/VariadicMacros.tests.cpp - ${SELF_TEST_DIR}/UsageTests/MatchersRanges.tests.cpp ${SELF_TEST_DIR}/UsageTests/Matchers.tests.cpp + ${SELF_TEST_DIR}/UsageTests/MatchersConstexpr.tests.cpp + ${SELF_TEST_DIR}/UsageTests/MatchersRanges.tests.cpp ) set(TEST_HEADERS diff --git a/tests/ExtraTests/CMakeLists.txt b/tests/ExtraTests/CMakeLists.txt index 886c059e..731bcf1c 100644 --- a/tests/ExtraTests/CMakeLists.txt +++ b/tests/ExtraTests/CMakeLists.txt @@ -174,7 +174,7 @@ target_compile_definitions(DeferredStaticChecks PRIVATE "CATCH_CONFIG_RUNTIME_ST add_test(NAME DeferredStaticChecks COMMAND DeferredStaticChecks -r compact) set_tests_properties(DeferredStaticChecks PROPERTIES - PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed" + PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed;test cases: 1 \\| 1 failed\nassertions: 4 \\| 4 failed" ) add_executable(MixingClearedAndUnclearedMessages ${TESTS_DIR}/X06-MixingClearedAndUnclearedMessages.cpp) diff --git a/tests/ExtraTests/X02-DisabledMacros.cpp b/tests/ExtraTests/X02-DisabledMacros.cpp index 0c051acc..3449ab73 100644 --- a/tests/ExtraTests/X02-DisabledMacros.cpp +++ b/tests/ExtraTests/X02-DisabledMacros.cpp @@ -53,6 +53,8 @@ TEST_CASE( "Disabled Macros" ) { REQUIRE_THAT( 1, Catch::Matchers::Predicate( []( int ) { return false; } ) ); BENCHMARK( "Disabled benchmark" ) { REQUIRE( 1 == 2 ); }; + + STATIC_REQUIRE_THAT( 1, Catch::Matchers::Predicate( []( int ) { return false; } ) ); } struct DisabledFixture {}; diff --git a/tests/ExtraTests/X05-DeferredStaticChecks.cpp b/tests/ExtraTests/X05-DeferredStaticChecks.cpp index 8005dbcf..79ff35cc 100644 --- a/tests/ExtraTests/X05-DeferredStaticChecks.cpp +++ b/tests/ExtraTests/X05-DeferredStaticChecks.cpp @@ -12,10 +12,36 @@ */ #include +#include + +#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED ) + +namespace { + struct MatchNoneMatcher final : public Catch::Matchers::MatcherGenericBase { + public: + template + constexpr bool match( Any&& ) const { + return false; + } + + std::string describe() const override { + using namespace std::string_literals; + return "Matches anything"s; + } + }; + + constexpr MatchNoneMatcher MatchNone() { return MatchNoneMatcher(); } + +} // namespace + +#endif TEST_CASE("Deferred static checks") { STATIC_CHECK(1 == 2); STATIC_CHECK_FALSE(1 != 2); +#if defined(CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED) + STATIC_CHECK_THAT(1, MatchNone()); +#endif // This last assertion must be executed too CHECK(1 == 2); } diff --git a/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp b/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp new file mode 100644 index 00000000..edd03519 --- /dev/null +++ b/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp @@ -0,0 +1,52 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +#include +#include + +#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED ) + +namespace { + struct MatchAllMatcher final : public Catch::Matchers::MatcherGenericBase { + public: + template + constexpr bool match( Any&& ) const { + return true; + } + + std::string describe() const override { + using namespace std::string_literals; + return "Matches anything"s; + } + }; + + constexpr MatchAllMatcher MatchAll() { return MatchAllMatcher(); } + +} // namespace + +TEST_CASE( "Constexpr support for matchers", "[constexpr][matchers][approvals]" ) { + STATIC_REQUIRE( MatchAll().match( 1 ) ); + STATIC_REQUIRE_THAT( 1, MatchAll() ); +} + +// Combining matchers needs C++26 and P2738, so they are in separate preprocessor block +# if __cpp_constexpr >= 202306L + +TEST_CASE("Constexpr support for combining matchers", + "[constexpr][matchers][approvals]") { + STATIC_REQUIRE( ( MatchAll() && MatchAll() ).match( 1 ) ); + STATIC_REQUIRE( ( MatchAll() || MatchAll() ).match( 1 ) ); + STATIC_REQUIRE( ( !!MatchAll() ).match( 1 ) ); + STATIC_REQUIRE_THAT( 1, MatchAll() && MatchAll() ); + STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() ); + STATIC_REQUIRE_THAT( 1, !!MatchAll() ); +} + +#endif // __cpp_constexpr >= 202306L + +#endif diff --git a/tests/meson.build b/tests/meson.build index 58302b7a..0e44eaab 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -49,6 +49,7 @@ self_test_sources = files( 'SelfTest/UsageTests/Exception.tests.cpp', 'SelfTest/UsageTests/Generators.tests.cpp', 'SelfTest/UsageTests/Matchers.tests.cpp', + 'SelfTest/UsageTests/MatchersConstexpr.tests.cpp', 'SelfTest/UsageTests/MatchersRanges.tests.cpp', 'SelfTest/UsageTests/Message.tests.cpp', 'SelfTest/UsageTests/Misc.tests.cpp',