diff --git a/docs/matchers.md b/docs/matchers.md index 91fff807b2..8a6d979117 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -147,7 +147,7 @@ REQUIRE_THROWS_MATCHES(throwsDerivedException(), DerivedException, Message("De It's easy to provide your own matchers to extend Catch or just to work with your own types. You need to provide two things: -1. A matcher class, derived from `Catch::MatcherBase` - where `T` is the matcher class. +1. A matcher class, derived from `Catch::MatcherBaseGeneric` - where `T` is the matcher class. The constructor takes and stores any arguments needed (e.g. something to compare against) and you must provide two methods: `match()` and `describe()`. 2. A simple builder function. This is what is actually called from the test code and allows overloading. @@ -157,7 +157,7 @@ Here's an example for asserting that an integer falls within a given range ```c++ // The matcher class -class IntRange : public Catch::MatcherBase { +class IntRange : public Catch::MatcherBaseGeneric { int m_begin, m_end; public: IntRange( int begin, int end ) : m_begin( begin ), m_end( end ) {} diff --git a/src/catch2/catch_capture_matchers.h b/src/catch2/catch_capture_matchers.h index d9945005cf..d96126d91b 100644 --- a/src/catch2/catch_capture_matchers.h +++ b/src/catch2/catch_capture_matchers.h @@ -42,7 +42,7 @@ namespace Catch { // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers // the Equals matcher (so the header does not mention matchers) template, StringMatcher>::value>::type> + typename = typename std::enable_if, StringMatcher>::value>::type> void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { std::string exceptionMessage = Catch::translateActiveException(); MatchExpr expr( exceptionMessage, matcher, matcherString ); diff --git a/src/catch2/catch_matchers.h b/src/catch2/catch_matchers.h index 67926d6437..86e06451c4 100644 --- a/src/catch2/catch_matchers.h +++ b/src/catch2/catch_matchers.h @@ -22,11 +22,11 @@ namespace Matchers { template struct MatchNotOf; template - class MatcherBase { + class MatcherBaseGeneric { public: - MatcherBase() = default; - MatcherBase ( MatcherBase const& ) = default; - MatcherBase& operator = ( MatcherBase const& ) = delete; + MatcherBaseGeneric() = default; + MatcherBaseGeneric ( MatcherBaseGeneric const& ) = default; + MatcherBaseGeneric& operator = ( MatcherBaseGeneric const& ) = delete; std::string toString() const; template @@ -44,7 +44,7 @@ namespace Matchers { }; template - std::string MatcherBase::toString() const { + std::string MatcherBaseGeneric::toString() const { if( m_cachedToString.empty() ) m_cachedToString = derived().describe(); return m_cachedToString; @@ -54,7 +54,7 @@ namespace Matchers { using Tuple = std::tuple::type>::type ...>; template - struct MatchAllOf : MatcherBase> { + struct MatchAllOf : MatcherBaseGeneric> { MatchAllOf( Matchers const &... matchers ) : m_matchers{matchers...} {} @@ -116,7 +116,7 @@ namespace Matchers { }; template - struct MatchAnyOf : MatcherBase> { + struct MatchAnyOf : MatcherBaseGeneric> { MatchAnyOf( Matchers const &... matchers ) : m_matchers{matchers...} {} @@ -178,7 +178,7 @@ namespace Matchers { }; template - struct MatchNotOf : MatcherBase> { + struct MatchNotOf : MatcherBaseGeneric> { MatchNotOf( Matcher const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} @@ -195,26 +195,54 @@ namespace Matchers { template template - MatchAllOf MatcherBase::operator && ( Other const& other ) const { + MatchAllOf MatcherBaseGeneric::operator && ( Other const& other ) const { return {derived(), other}; } template template - MatchAnyOf MatcherBase::operator || ( Other const& other ) const { + MatchAnyOf MatcherBaseGeneric::operator || ( Other const& other ) const { return {derived(), other}; } template - MatchNotOf MatcherBase::operator ! () const { + MatchNotOf MatcherBaseGeneric::operator ! () const { return {derived()}; } + template + Derived const& operator ! (MatchNotOf const& matcher) { + return matcher.m_underlyingMatcher; + } + + + template + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + +#if defined(__OBJC__) + // Hack to fix Catch GH issue #1661. Could use id for generic Object support. + // use of const for Object pointers is very uncommon and under ARC it causes some kind of signature mismatch that breaks compilation + template<> + struct MatcherMethod { + virtual bool match( NSString* arg ) const = 0; + }; +#endif + + // for backward compatibility + template + struct MatcherBase : MatcherBaseGeneric>, MatcherMethod { + virtual ~MatcherBase() = default; + virtual std::string describe() const = 0; + }; + } // namespace Impl } // namespace Matchers using namespace Matchers; +using Matchers::Impl::MatcherBaseGeneric; using Matchers::Impl::MatcherBase; } // namespace Catch diff --git a/src/catch2/catch_matchers_exception.hpp b/src/catch2/catch_matchers_exception.hpp index dc30542d3d..38696babd7 100644 --- a/src/catch2/catch_matchers_exception.hpp +++ b/src/catch2/catch_matchers_exception.hpp @@ -13,7 +13,7 @@ namespace Catch { namespace Matchers { namespace Exception { -class ExceptionMessageMatcher : public MatcherBase { +class ExceptionMessageMatcher : public MatcherBaseGeneric { std::string m_message; public: diff --git a/src/catch2/catch_matchers_floating.h b/src/catch2/catch_matchers_floating.h index 0bf8e7e650..6057b69311 100644 --- a/src/catch2/catch_matchers_floating.h +++ b/src/catch2/catch_matchers_floating.h @@ -16,7 +16,7 @@ namespace Matchers { enum class FloatingPointKind : uint8_t; - struct WithinAbsMatcher : MatcherBase { + struct WithinAbsMatcher : MatcherBaseGeneric { WithinAbsMatcher(double target, double margin); bool match(double const& matchee) const; std::string describe() const; @@ -25,7 +25,7 @@ namespace Matchers { double m_margin; }; - struct WithinUlpsMatcher : MatcherBase { + struct WithinUlpsMatcher : MatcherBaseGeneric { WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType); bool match(double const& matchee) const; std::string describe() const; @@ -41,7 +41,7 @@ namespace Matchers { // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get // the same result if we do this for floats, as if we do this for // doubles that were promoted from floats. - struct WithinRelMatcher : MatcherBase { + struct WithinRelMatcher : MatcherBaseGeneric { WithinRelMatcher(double target, double epsilon); bool match(double const& matchee) const; std::string describe() const; diff --git a/src/catch2/catch_matchers_generic.hpp b/src/catch2/catch_matchers_generic.hpp index a85955bd69..903b9b62fc 100644 --- a/src/catch2/catch_matchers_generic.hpp +++ b/src/catch2/catch_matchers_generic.hpp @@ -23,13 +23,14 @@ namespace Detail { } template -class PredicateMatcher : public MatcherBase> { +class PredicateMatcher : public MatcherBaseGeneric> { Predicate m_predicate; std::string m_description; public: - PredicateMatcher(Predicate&& elem, std::string const& descr) - :m_predicate(std::forward(elem)), + template + PredicateMatcher(_Predicate&& elem, std::string const& descr) + :m_predicate(std::forward<_Predicate>(elem)), m_description(Detail::finalizeDescription(descr)) {} @@ -51,6 +52,13 @@ class PredicateMatcher : public MatcherBase> { // The user has to explicitly specify type to the function, because // inferring std::function is hard (but possible) and // requires a lot of TMP. + template + Generic::PredicateMatcher Predicate(Pred&& predicate, std::string const& description = "") { + static_assert(is_callable::value, "Predicate not callable with argument T"); + static_assert(std::is_same>::value, "Predicate does not return bool"); + return Generic::PredicateMatcher(std::forward(predicate), description); + } + template Generic::PredicateMatcher Predicate(Pred&& predicate, std::string const& description = "") { return Generic::PredicateMatcher(std::forward(predicate), description); diff --git a/src/catch2/catch_matchers_string.h b/src/catch2/catch_matchers_string.h index 51ac62223a..8225859427 100644 --- a/src/catch2/catch_matchers_string.h +++ b/src/catch2/catch_matchers_string.h @@ -28,7 +28,7 @@ namespace Matchers { }; template - struct StringMatcherBase : MatcherBase { + struct StringMatcherBase : MatcherBaseGeneric { StringMatcherBase( std::string const& operation, CasedString const& comparator ); std::string describe() const ; @@ -73,7 +73,7 @@ namespace Matchers { bool match( std::string const& source ) const ; }; - struct RegexMatcher : MatcherBase { + struct RegexMatcher : MatcherBaseGeneric { RegexMatcher( std::string regex, CaseSensitive::Choice caseSensitivity ); bool match( std::string const& matchee ) const ; std::string describe() const ; diff --git a/src/catch2/catch_matchers_vector.h b/src/catch2/catch_matchers_vector.h index c4e1fd2284..a987ec2923 100644 --- a/src/catch2/catch_matchers_vector.h +++ b/src/catch2/catch_matchers_vector.h @@ -18,7 +18,7 @@ namespace Matchers { namespace Vector { template - struct ContainsElementMatcher : MatcherBase> { + struct ContainsElementMatcher : MatcherBaseGeneric> { ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} @@ -39,7 +39,7 @@ namespace Matchers { }; template - struct ContainsMatcher : MatcherBase> { + struct ContainsMatcher : MatcherBaseGeneric> { ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} @@ -69,7 +69,7 @@ namespace Matchers { }; template - struct EqualsMatcher : MatcherBase> { + struct EqualsMatcher : MatcherBaseGeneric> { EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} @@ -92,7 +92,7 @@ namespace Matchers { }; template - struct ApproxMatcher : MatcherBase> { + struct ApproxMatcher : MatcherBaseGeneric> { ApproxMatcher(std::vector const& comparator) : m_comparator( comparator ) {} @@ -128,7 +128,7 @@ namespace Matchers { }; template - struct UnorderedEqualsMatcher : MatcherBase> { + struct UnorderedEqualsMatcher : MatcherBaseGeneric> { UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} bool match(std::vector const& vec) const { // Note: This is a reimplementation of std::is_permutation, diff --git a/src/catch2/reporters/catch_reporter_teamcity.hpp b/src/catch2/reporters/catch_reporter_teamcity.hpp index 77d74f9747..42f9518fd0 100644 --- a/src/catch2/reporters/catch_reporter_teamcity.hpp +++ b/src/catch2/reporters/catch_reporter_teamcity.hpp @@ -33,7 +33,7 @@ namespace Catch { void skipTest( TestCaseInfo const& /* testInfo */ ) override {} - void nomatchingTestCases( std::string const& /* spec */ ) {} + void noMatchingTestCases( std::string const& /* spec */ ) {} void testGroupStarting(GroupInfo const& groupInfo) override; void testGroupEnded(TestGroupStats const& testGroupStats) override; diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 7169655cf3..42b5ac6e0f 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -85,6 +85,12 @@ Nor would this :test-result: PASS CAPTURE parses string and character constants :test-result: PASS Capture and info messages :test-result: PASS Character pretty printing +:test-result: PASS Combining MatchAllOf does not nest +:test-result: PASS Combining MatchAnyOf does not nest +:test-result: PASS Combining MatchNotOf does not nest +:test-result: PASS Combining generic and non-generic matchers +:test-result: PASS Combining generic matchers +:test-result: PASS Combining only templated matchers :test-result: PASS Commas in various macros are allowed :test-result: PASS Comparing function pointers :test-result: PASS Comparison ops @@ -127,10 +133,12 @@ Nor would this :test-result: FAIL INFO gets logged on failure :test-result: FAIL INFO gets logged on failure, even if captured before successful assertions :test-result: FAIL INFO is reset for each loop +:test-result: PASS Immovable matchers can be used :test-result: XFAIL Inequality checks that should fail :test-result: PASS Inequality checks that should succeed :test-result: PASS Less-than inequalities with different epsilons :test-result: PASS ManuallyRegistered +:test-result: PASS Matchers are not moved or copied :test-result: PASS Matchers can be (AllOf) composed with the && operator :test-result: PASS Matchers can be (AnyOf) composed with the || operator :test-result: PASS Matchers can be composed with both && and || @@ -147,6 +155,7 @@ Nor would this :test-result: PASS Ordering comparison checks that should succeed :test-result: PASS Our PCG implementation provides expected results for known seeds :test-result: FAIL Output from all sections is reported +:test-result: PASS Overloaded comma or address-of operators are not used :test-result: PASS Parse test names and tags :test-result: PASS Pointers can be compared to null :test-result: PASS Precision of floating point stringification can be set diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index b8ef5e8400..2b37612223 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -263,6 +263,30 @@ ToStringGeneral.tests.cpp:: passed: c == i for: 2 == 2 ToStringGeneral.tests.cpp:: passed: c == i for: 3 == 3 ToStringGeneral.tests.cpp:: passed: c == i for: 4 == 4 ToStringGeneral.tests.cpp:: passed: c == i for: 5 == 5 +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() && MatcherB() && MatcherC() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() && MatcherB() && MatcherC() && MatcherD() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 and equals: true ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() || MatcherB() || MatcherC() for: 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() || MatcherB() || MatcherC() || MatcherD() for: 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 or equals: true ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 0, !MatcherA() for: 0 not equals: (int) 1 or (float) 1.0f +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, !!MatcherA() for: 1 equals: (int) 1 or (float) 1.0f +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 0, !!!MatcherA() for: 0 not equals: (int) 1 or (float) 1.0f +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, !!!!MatcherA() for: 1 equals: (int) 1 or (float) 1.0f +Matchers.tests.cpp:: passed: vec, AllOf([](int elem) { return elem % 2 == 1; }, "odd") && !EqualsRange(a) for: { 1, 3, 5 } ( all of are odd and not Equals: { 5, 3, 1 } ) +Matchers.tests.cpp:: passed: container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c) for: { 1, 2, 3 } ( Equals: { 1, 2, 3 } or Equals: { 0, 1, 2 } or Equals: { 4, 5, 6 } ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() || MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() && MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 ) +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same::value' +Matchers.tests.cpp:: passed: 1, MatcherA() || !MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f or not equals: (long long) 1 ) Tricky.tests.cpp:: passed: std::vector{constructor_throws{}, constructor_throws{}} Tricky.tests.cpp:: passed: std::vector{constructor_throws{}, constructor_throws{}} Tricky.tests.cpp:: passed: std::vector{1, 2, 3} == std::vector{1, 2, 3} @@ -747,6 +771,7 @@ Message.tests.cpp:: passed: i < 10 for: 7 < 10 with 2 messages: 'cu Message.tests.cpp:: passed: i < 10 for: 8 < 10 with 2 messages: 'current counter 8' and 'i := 8' Message.tests.cpp:: passed: i < 10 for: 9 < 10 with 2 messages: 'current counter 9' and 'i := 9' Message.tests.cpp:: failed: i < 10 for: 10 < 10 with 2 messages: 'current counter 10' and 'i := 10' +Matchers.tests.cpp:: passed: 123, (ImmovableMatcher() && ImmovableMatcher()) || !ImmovableMatcher() for: 123 ( ( always false and always false ) or not always false ) Condition.tests.cpp:: failed: data.int_seven != 7 for: 7 != 7 Condition.tests.cpp:: failed: data.float_nine_point_one != Approx( 9.1f ) for: 9.1f != Approx( 9.1000003815 ) Condition.tests.cpp:: failed: data.double_pi != Approx( 3.1415926535 ) for: 3.1415926535 != Approx( 3.1415926535 ) @@ -768,6 +793,7 @@ Approx.tests.cpp:: passed: d <= Approx( 1.23 ) for: 1.23 <= Approx( Approx.tests.cpp:: passed: !(d <= Approx( 1.22 )) for: !(1.23 <= Approx( 1.22 )) Approx.tests.cpp:: passed: d <= Approx( 1.22 ).epsilon(0.1) for: 1.23 <= Approx( 1.22 ) Misc.tests.cpp:: passed: with 1 message: 'was called' +Matchers.tests.cpp:: passed: (ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher() Matchers.tests.cpp:: passed: testStringForMatching(), Contains("string") && Contains("abc") && Contains("substring") && Contains("contains") for: "this string contains 'abc' as a substring" ( contains: "string" and contains: "abc" and contains: "substring" and contains: "contains" ) Matchers.tests.cpp:: passed: testStringForMatching(), Contains("string") || Contains("different") || Contains("random") for: "this string contains 'abc' as a substring" ( contains: "string" or contains: "different" or contains: "random" ) Matchers.tests.cpp:: passed: testStringForMatching2(), Contains("string") || Contains("different") || Contains("random") for: "some completely different text that contains one common word" ( contains: "string" or contains: "different" or contains: "random" ) @@ -883,6 +909,10 @@ RandomNumberGeneration.tests.cpp:: passed: rng() == 0x 4261393167 (0x) Message.tests.cpp:: failed: explicitly with 1 message: 'Message from section one' Message.tests.cpp:: failed: explicitly with 1 message: 'Message from section two' +Matchers.tests.cpp:: passed: (EvilMatcher(), EvilMatcher()), EvilCommaOperatorUsed +Matchers.tests.cpp:: passed: &EvilMatcher(), EvilAddressOfOperatorUsed +Matchers.tests.cpp:: passed: EvilMatcher() || EvilMatcher() && !EvilMatcher() +Matchers.tests.cpp:: passed: (EvilMatcher() && EvilMatcher()) || !EvilMatcher() CmdLine.tests.cpp:: passed: spec.hasFilters() == false for: false == false CmdLine.tests.cpp:: passed: spec.matches( *tcA ) == false for: false == false CmdLine.tests.cpp:: passed: spec.matches( *tcB ) == false for: false == false diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index b5dd9229f0..efe0d54577 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1380,6 +1380,6 @@ due to unexpected exception with message: Why would you throw a std::string? =============================================================================== -test cases: 321 | 247 passed | 70 failed | 4 failed as expected -assertions: 1777 | 1625 passed | 131 failed | 21 failed as expected +test cases: 330 | 256 passed | 70 failed | 4 failed as expected +assertions: 1807 | 1655 passed | 131 failed | 21 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index b6c2d54521..ae2a8d9239 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -2116,6 +2116,156 @@ ToStringGeneral.tests.cpp:: PASSED: with expansion: 5 == 5 +------------------------------------------------------------------------------- +Combining MatchAllOf does not nest +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() && MatcherB() && MatcherC() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) + 1 ) + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() && MatcherB() && MatcherC() && MatcherD() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) + 1 and equals: true ) + +------------------------------------------------------------------------------- +Combining MatchAnyOf does not nest +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() || MatcherB() || MatcherC() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 + ) + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() || MatcherB() || MatcherC() || MatcherD() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 + or equals: true ) + +------------------------------------------------------------------------------- +Combining MatchNotOf does not nest +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 0, !MatcherA() ) +with expansion: + 0 not equals: (int) 1 or (float) 1.0f + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, !!MatcherA() ) +with expansion: + 1 equals: (int) 1 or (float) 1.0f + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 0, !!!MatcherA() ) +with expansion: + 0 not equals: (int) 1 or (float) 1.0f + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, !!!!MatcherA() ) +with expansion: + 1 equals: (int) 1 or (float) 1.0f + +------------------------------------------------------------------------------- +Combining generic and non-generic matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( vec, AllOf([](int elem) { return elem % 2 == 1; }, "odd") && !EqualsRange(a) ) +with expansion: + { 1, 3, 5 } ( all of are odd and not Equals: { 5, 3, 1 } ) + +------------------------------------------------------------------------------- +Combining generic matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c) ) +with expansion: + { 1, 2, 3 } ( Equals: { 1, 2, 3 } or Equals: { 0, 1, 2 } or Equals: { 4, 5, 6 + } ) + +------------------------------------------------------------------------------- +Combining only templated matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() || MatcherB() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 ) + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() && MatcherB() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 ) + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, MatcherA() || !MatcherB() ) +with expansion: + 1 ( equals: (int) 1 or (float) 1.0f or not equals: (long long) 1 ) + ------------------------------------------------------------------------------- Commas in various macros are allowed ------------------------------------------------------------------------------- @@ -5628,6 +5778,17 @@ with messages: current counter 10 i := 10 +------------------------------------------------------------------------------- +Immovable matchers can be used +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 123, (ImmovableMatcher() && ImmovableMatcher()) || !ImmovableMatcher() ) +with expansion: + 123 ( ( always false and always false ) or not always false ) + ------------------------------------------------------------------------------- Inequality checks that should fail ------------------------------------------------------------------------------- @@ -5756,6 +5917,15 @@ Misc.tests.cpp:: PASSED: with message: was called +------------------------------------------------------------------------------- +Matchers are not moved or copied +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_NOTHROW( (ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher() ) + ------------------------------------------------------------------------------- Matchers can be (AllOf) composed with the && operator ------------------------------------------------------------------------------- @@ -6414,6 +6584,24 @@ Message.tests.cpp:: FAILED: explicitly with message: Message from section two +------------------------------------------------------------------------------- +Overloaded comma or address-of operators are not used +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( (EvilMatcher(), EvilMatcher()), EvilCommaOperatorUsed ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THROWS_AS( &EvilMatcher(), EvilAddressOfOperatorUsed ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_NOTHROW( EvilMatcher() || EvilMatcher() && !EvilMatcher() ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_NOTHROW( (EvilMatcher() && EvilMatcher()) || !EvilMatcher() ) + ------------------------------------------------------------------------------- Parse test names and tags Empty test spec should have no filters @@ -13971,6 +14159,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 321 | 231 passed | 86 failed | 4 failed as expected -assertions: 1794 | 1625 passed | 148 failed | 21 failed as expected +test cases: 330 | 240 passed | 86 failed | 4 failed as expected +assertions: 1824 | 1655 passed | 148 failed | 21 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 59e1200f2c..e2d9a6e1e4 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -350,6 +350,12 @@ Exception.tests.cpp: + + + + + + @@ -715,6 +721,7 @@ i := 10 Message.tests.cpp: + FAILED: @@ -755,6 +762,7 @@ Condition.tests.cpp: + @@ -950,6 +958,7 @@ Message from section two Message.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index 09ed3f70b2..82013eb5f1 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -912,6 +912,12 @@ Exception.tests.cpp: + + + + + + FAILED: @@ -1017,6 +1023,8 @@ Matchers.tests.cpp: + + @@ -1039,6 +1047,7 @@ with expansion: Matchers.tests.cpp: + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index aef42abe3e..9051d4dc9c 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -524,6 +524,54 @@ ok {test-number} - c == i for: 3 == 3 ok {test-number} - c == i for: 4 == 4 # Character pretty printing ok {test-number} - c == i for: 5 == 5 +# Combining MatchAllOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchAllOf does not nest +ok {test-number} - 1, MatcherA() && MatcherB() && MatcherC() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 ) +# Combining MatchAllOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchAllOf does not nest +ok {test-number} - 1, MatcherA() && MatcherB() && MatcherC() && MatcherD() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 and equals: true ) +# Combining MatchAnyOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchAnyOf does not nest +ok {test-number} - 1, MatcherA() || MatcherB() || MatcherC() for: 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 ) +# Combining MatchAnyOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchAnyOf does not nest +ok {test-number} - 1, MatcherA() || MatcherB() || MatcherC() || MatcherD() for: 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 or equals: true ) +# Combining MatchNotOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchNotOf does not nest +ok {test-number} - 0, !MatcherA() for: 0 not equals: (int) 1 or (float) 1.0f +# Combining MatchNotOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchNotOf does not nest +ok {test-number} - 1, !!MatcherA() for: 1 equals: (int) 1 or (float) 1.0f +# Combining MatchNotOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchNotOf does not nest +ok {test-number} - 0, !!!MatcherA() for: 0 not equals: (int) 1 or (float) 1.0f +# Combining MatchNotOf does not nest +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining MatchNotOf does not nest +ok {test-number} - 1, !!!!MatcherA() for: 1 equals: (int) 1 or (float) 1.0f +# Combining generic and non-generic matchers +ok {test-number} - vec, AllOf([](int elem) { return elem % 2 == 1; }, "odd") && !EqualsRange(a) for: { 1, 3, 5 } ( all of are odd and not Equals: { 5, 3, 1 } ) +# Combining generic matchers +ok {test-number} - container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c) for: { 1, 2, 3 } ( Equals: { 1, 2, 3 } or Equals: { 0, 1, 2 } or Equals: { 4, 5, 6 } ) +# Combining only templated matchers +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining only templated matchers +ok {test-number} - 1, MatcherA() || MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 ) +# Combining only templated matchers +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining only templated matchers +ok {test-number} - 1, MatcherA() && MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 ) +# Combining only templated matchers +ok {test-number} - with 1 message: 'std::is_same::value' +# Combining only templated matchers +ok {test-number} - 1, MatcherA() || !MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f or not equals: (long long) 1 ) # Commas in various macros are allowed ok {test-number} - std::vector{constructor_throws{}, constructor_throws{}} # Commas in various macros are allowed @@ -795,7 +843,7 @@ not ok {test-number} - explicitly # FAIL_CHECK does not abort the test not ok {test-number} - explicitly with 1 message: 'This is a failure' # FAIL_CHECK does not abort the test -warning 397 - 'This message appears in the output' +warning 421 - 'This message appears in the output' # Factorials are computed ok {test-number} - Factorial(0) == 1 for: 1 == 1 # Factorials are computed @@ -1443,7 +1491,7 @@ ok {test-number} - !(d >= Approx( 1.24 )) for: !(1.23 >= Approx( 1.24 )) # Greater-than inequalities with different epsilons ok {test-number} - d >= Approx( 1.24 ).epsilon(0.1) for: 1.23 >= Approx( 1.24 ) # INFO and WARN do not abort tests -warning 721 - 'this is a message' with 1 message: 'this is a warning' +warning 745 - 'this is a message' with 1 message: 'this is a warning' # INFO gets logged on failure not ok {test-number} - a == 1 for: 2 == 1 with 2 messages: 'this message should be logged' and 'so should this' # INFO gets logged on failure, even if captured before successful assertions @@ -1476,6 +1524,8 @@ ok {test-number} - i < 10 for: 8 < 10 with 2 messages: 'current counter 8' and ' ok {test-number} - i < 10 for: 9 < 10 with 2 messages: 'current counter 9' and 'i := 9' # INFO is reset for each loop not ok {test-number} - i < 10 for: 10 < 10 with 2 messages: 'current counter 10' and 'i := 10' +# Immovable matchers can be used +ok {test-number} - 123, (ImmovableMatcher() && ImmovableMatcher()) || !ImmovableMatcher() for: 123 ( ( always false and always false ) or not always false ) # Inequality checks that should fail not ok {test-number} - data.int_seven != 7 for: 7 != 7 # Inequality checks that should fail @@ -1518,6 +1568,8 @@ ok {test-number} - !(d <= Approx( 1.22 )) for: !(1.23 <= Approx( 1.22 )) ok {test-number} - d <= Approx( 1.22 ).epsilon(0.1) for: 1.23 <= Approx( 1.22 ) # ManuallyRegistered ok {test-number} - with 1 message: 'was called' +# Matchers are not moved or copied +ok {test-number} - (ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher() # Matchers can be (AllOf) composed with the && operator ok {test-number} - testStringForMatching(), Contains("string") && Contains("abc") && Contains("substring") && Contains("contains") for: "this string contains 'abc' as a substring" ( contains: "string" and contains: "abc" and contains: "substring" and contains: "contains" ) # Matchers can be (AnyOf) composed with the || operator @@ -1569,7 +1621,7 @@ ok {test-number} - values > -6 for: 98 > -6 # Nested generators and captured variables ok {test-number} - values > -6 for: 99 > -6 # Nice descriptive name -warning 784 - 'This one ran' +warning 810 - 'This one ran' # Non-std exceptions can be translated not ok {test-number} - unexpected exception with message: 'custom exception' # Objects that evaluated in boolean contexts can be checked @@ -1688,6 +1740,14 @@ ok {test-number} - rng() == 0x for: 4261393167 (0x) == 4 not ok {test-number} - explicitly with 1 message: 'Message from section one' # Output from all sections is reported not ok {test-number} - explicitly with 1 message: 'Message from section two' +# Overloaded comma or address-of operators are not used +ok {test-number} - (EvilMatcher(), EvilMatcher()), EvilCommaOperatorUsed +# Overloaded comma or address-of operators are not used +ok {test-number} - &EvilMatcher(), EvilAddressOfOperatorUsed +# Overloaded comma or address-of operators are not used +ok {test-number} - EvilMatcher() || EvilMatcher() && !EvilMatcher() +# Overloaded comma or address-of operators are not used +ok {test-number} - (EvilMatcher() && EvilMatcher()) || !EvilMatcher() # Parse test names and tags ok {test-number} - spec.hasFilters() == false for: false == false # Parse test names and tags @@ -2928,9 +2988,9 @@ not ok {test-number} - unexpected exception with message: 'expected exception'; # When unchecked exceptions are thrown from sections they are always failures not ok {test-number} - unexpected exception with message: 'unexpected exception' # Where the LHS is not a simple value -warning 1461 - 'Uncomment the code in this test to check that it gives a sensible compiler error' +warning 1491 - 'Uncomment the code in this test to check that it gives a sensible compiler error' # Where there is more to the expression after the RHS -warning 1462 - 'Uncomment the code in this test to check that it gives a sensible compiler error' +warning 1492 - 'Uncomment the code in this test to check that it gives a sensible compiler error' # X/level/0/a ok {test-number} - # X/level/0/b @@ -3197,9 +3257,9 @@ ok {test-number} - s.result == 17 for: 17 == 17 # measure ok {test-number} - s.iterations == 1 for: 1 == 1 # mix info, unscoped info and warning -warning 1595 - 'info' with 2 messages: 'unscoped info' and 'and warn may mix' +warning 1625 - 'info' with 2 messages: 'unscoped info' and 'and warn may mix' # mix info, unscoped info and warning -warning 1596 - 'info' with 2 messages: 'unscoped info' and 'they are not cleared after warnings' +warning 1626 - 'info' with 2 messages: 'unscoped info' and 'they are not cleared after warnings' # more nested SECTION tests not ok {test-number} - a == b for: 1 == 2 # more nested SECTION tests @@ -3580,5 +3640,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..1786 +1..1816 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index fe61882124..ffa8343877 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -203,6 +203,18 @@ Exception.tests.cpp:|nunexpected exception with message:|n "unexpe ##teamcity[testFinished name='Capture and info messages' duration="{duration}"] ##teamcity[testStarted name='Character pretty printing'] ##teamcity[testFinished name='Character pretty printing' duration="{duration}"] +##teamcity[testStarted name='Combining MatchAllOf does not nest'] +##teamcity[testFinished name='Combining MatchAllOf does not nest' duration="{duration}"] +##teamcity[testStarted name='Combining MatchAnyOf does not nest'] +##teamcity[testFinished name='Combining MatchAnyOf does not nest' duration="{duration}"] +##teamcity[testStarted name='Combining MatchNotOf does not nest'] +##teamcity[testFinished name='Combining MatchNotOf does not nest' duration="{duration}"] +##teamcity[testStarted name='Combining generic and non-generic matchers'] +##teamcity[testFinished name='Combining generic and non-generic matchers' duration="{duration}"] +##teamcity[testStarted name='Combining generic matchers'] +##teamcity[testFinished name='Combining generic matchers' duration="{duration}"] +##teamcity[testStarted name='Combining only templated matchers'] +##teamcity[testFinished name='Combining only templated matchers' duration="{duration}"] ##teamcity[testStarted name='Commas in various macros are allowed'] ##teamcity[testFinished name='Commas in various macros are allowed' duration="{duration}"] ##teamcity[testStarted name='Comparing function pointers'] @@ -325,6 +337,8 @@ Message.tests.cpp:|nexpression failed with messages:|n "this messa ##teamcity[testStarted name='INFO is reset for each loop'] Message.tests.cpp:|nexpression failed with messages:|n "current counter 10"|n "i := 10"|n REQUIRE( i < 10 )|nwith expansion:|n 10 < 10|n'] ##teamcity[testFinished name='INFO is reset for each loop' duration="{duration}"] +##teamcity[testStarted name='Immovable matchers can be used'] +##teamcity[testFinished name='Immovable matchers can be used' duration="{duration}"] ##teamcity[testStarted name='Inequality checks that should fail'] Condition.tests.cpp:|nexpression failed|n CHECK( data.int_seven != 7 )|nwith expansion:|n 7 != 7|n- failure ignore as test marked as |'ok to fail|'|n'] Condition.tests.cpp:|nexpression failed|n CHECK( data.float_nine_point_one != Approx( 9.1f ) )|nwith expansion:|n 9.1f != Approx( 9.1000003815 )|n- failure ignore as test marked as |'ok to fail|'|n'] @@ -338,6 +352,8 @@ Condition.tests.cpp:|nexpression failed|n CHECK( data.str_hello.si ##teamcity[testFinished name='Less-than inequalities with different epsilons' duration="{duration}"] ##teamcity[testStarted name='ManuallyRegistered'] ##teamcity[testFinished name='ManuallyRegistered' duration="{duration}"] +##teamcity[testStarted name='Matchers are not moved or copied'] +##teamcity[testFinished name='Matchers are not moved or copied' duration="{duration}"] ##teamcity[testStarted name='Matchers can be (AllOf) composed with the && operator'] ##teamcity[testFinished name='Matchers can be (AllOf) composed with the && operator' duration="{duration}"] ##teamcity[testStarted name='Matchers can be (AnyOf) composed with the |||| operator'] @@ -395,6 +411,8 @@ Condition.tests.cpp:|nexpression failed|n CHECK( data.str_hello <= Message.tests.cpp:|nexplicit failure with message:|n "Message from section one"'] Message.tests.cpp:|nexplicit failure with message:|n "Message from section two"'] ##teamcity[testFinished name='Output from all sections is reported' duration="{duration}"] +##teamcity[testStarted name='Overloaded comma or address-of operators are not used'] +##teamcity[testFinished name='Overloaded comma or address-of operators are not used' duration="{duration}"] ##teamcity[testStarted name='Parse test names and tags'] ##teamcity[testFinished name='Parse test names and tags' duration="{duration}"] ##teamcity[testStarted name='Pointers can be compared to null'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 60bbaa6187..907f4ec8a5 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -2417,6 +2417,128 @@ Nor would this + + + + 1, MatcherA() && MatcherB() && MatcherC() + + + 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 ) + + + + + 1, MatcherA() && MatcherB() && MatcherC() && MatcherD() + + + 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 and equals: (T) 1 and equals: true ) + + + + + + + + 1, MatcherA() || MatcherB() || MatcherC() + + + 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 ) + + + + + 1, MatcherA() || MatcherB() || MatcherC() || MatcherD() + + + 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 or equals: (T) 1 or equals: true ) + + + + + + + + 0, !MatcherA() + + + 0 not equals: (int) 1 or (float) 1.0f + + + + + 1, !!MatcherA() + + + 1 equals: (int) 1 or (float) 1.0f + + + + + 0, !!!MatcherA() + + + 0 not equals: (int) 1 or (float) 1.0f + + + + + 1, !!!!MatcherA() + + + 1 equals: (int) 1 or (float) 1.0f + + + + + + + + vec, AllOf([](int elem) { return elem % 2 == 1; }, "odd") && !EqualsRange(a) + + + { 1, 3, 5 } ( all of are odd and not Equals: { 5, 3, 1 } ) + + + + + + + + container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c) + + + { 1, 2, 3 } ( Equals: { 1, 2, 3 } or Equals: { 0, 1, 2 } or Equals: { 4, 5, 6 } ) + + + + + + + + 1, MatcherA() || MatcherB() + + + 1 ( equals: (int) 1 or (float) 1.0f or equals: (long long) 1 ) + + + + + 1, MatcherA() && MatcherB() + + + 1 ( equals: (int) 1 or (float) 1.0f and equals: (long long) 1 ) + + + + + 1, MatcherA() || !MatcherB() + + + 1 ( equals: (int) 1 or (float) 1.0f or not equals: (long long) 1 ) + + + + @@ -7119,6 +7241,17 @@ Nor would this + + + + 123, (ImmovableMatcher() && ImmovableMatcher()) || !ImmovableMatcher() + + + 123 ( ( always false and always false ) or not always false ) + + + + @@ -7291,6 +7424,17 @@ Nor would this + + + + (ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher() + + + (ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher() + + + + @@ -8025,6 +8169,41 @@ Nor would this + + + + (EvilMatcher(), EvilMatcher()), EvilCommaOperatorUsed + + + (EvilMatcher(), EvilMatcher()), EvilCommaOperatorUsed + + + + + &EvilMatcher(), EvilAddressOfOperatorUsed + + + &EvilMatcher(), EvilAddressOfOperatorUsed + + + + + EvilMatcher() || EvilMatcher() && !EvilMatcher() + + + EvilMatcher() || EvilMatcher() && !EvilMatcher() + + + + + (EvilMatcher() && EvilMatcher()) || !EvilMatcher() + + + (EvilMatcher() && EvilMatcher()) || !EvilMatcher() + + + +
@@ -16852,7 +17031,7 @@ loose text artifact
- + - + diff --git a/tests/SelfTest/UsageTests/Matchers.tests.cpp b/tests/SelfTest/UsageTests/Matchers.tests.cpp index 0da3d85a67..cbbf5fee9c 100644 --- a/tests/SelfTest/UsageTests/Matchers.tests.cpp +++ b/tests/SelfTest/UsageTests/Matchers.tests.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #ifdef __clang__ #pragma clang diagnostic push @@ -76,7 +78,7 @@ namespace { namespace MatchersTests { throw DerivedException{}; } - class ExceptionMatcher : public Catch::MatcherBase { + class ExceptionMatcher : public Catch::MatcherBaseGeneric { int m_expected; public: ExceptionMatcher(int i) : m_expected(i) {} @@ -565,7 +567,260 @@ namespace { namespace MatchersTests { REQUIRE_THAT(s, Predicate([](S &s){ return s.value() == 42;})); } -} } // namespace MatchersTests + + template + struct EqualsRangeMatcher : Catch::MatcherBaseGeneric> { + + EqualsRangeMatcher(Range const& range) : range{ range } {} + + template + bool match(OtherRange const& other) const { + using std::begin; + using std::end; + + return std::equal(begin(range), end(range), begin(other), end(other)); + } + + std::string describe() const { + return "Equals: " + Catch::rangeToString(range); + } + + private: + Range const& range; + }; + + template + auto EqualsRange(const Range& range) -> EqualsRangeMatcher { + return EqualsRangeMatcher{range}; + } + + TEST_CASE("Combining generic matchers", "[matchers][templated]") { + std::array container{{ 1,2,3 }}; + + std::array a{{ 1,2,3 }}; + std::vector b{ 0,1,2 }; + std::list c{ 4,5,6 }; + + REQUIRE_THAT(container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c)); + } + + template + class AllOfMatcher : public Catch::MatcherBase> { + Predicate m_predicate; + std::string m_description; + + public: + template + AllOfMatcher(_Predicate &&predicate, const std::string &description) + : m_predicate{std::forward<_Predicate>(predicate)}, + m_description{description} + {} + + bool match(const std::vector &vec) const override { + return std::all_of(vec.begin(), vec.end(), m_predicate); + } + + std::string describe() const override { + return "all of are " + m_description; + } + + }; + + template + AllOfMatcher AllOf(Predicate &&predicate, const std::string &description) { + return {predicate, description}; + } + + TEST_CASE("Combining generic and non-generic matchers", "[matchers][templated]") { + using namespace Catch::Matchers; + std::vector vec{ 1, 3, 5 }; + + std::array a{{ 5, 3, 1 }}; + + REQUIRE_THAT(vec, AllOf([](int elem) { + return elem % 2 == 1; + }, "odd") && + !EqualsRange(a)); + } + + struct MatcherA : Catch::MatcherBaseGeneric { + std::string describe() const { return "equals: (int) 1 or (float) 1.0f"; } + bool match(int i) const { return i == 1; } + bool match(float f) const { return f == 1.0f; } + }; + + struct MatcherB : Catch::MatcherBaseGeneric { + std::string describe() const { return "equals: (long long) 1"; } + bool match(long long l) const { return l == 1ll; } + }; + + struct MatcherC : Catch::MatcherBaseGeneric { + std::string describe() const { return "equals: (T) 1"; } + template + bool match(T t) const { return t == T{1}; } + }; + + struct MatcherD : Catch::MatcherBaseGeneric { + std::string describe() const { return "equals: true"; } + bool match(bool b) const { return b == true; } + }; + + template + void has_type(Actual &&) + { + STATIC_REQUIRE(std::is_same::value); + } + + TEST_CASE("Combining only templated matchers", "[matchers][templated]") { + has_type>(MatcherA() || MatcherB()); + + REQUIRE_THAT(1, MatcherA() || MatcherB()); + + has_type>(MatcherA() && MatcherB()); + + REQUIRE_THAT(1, MatcherA() && MatcherB()); + + has_type>>(MatcherA() || !MatcherB()); + + REQUIRE_THAT(1, MatcherA() || !MatcherB()); + } + + TEST_CASE("Combining MatchAnyOf does not nest", "[matchers][templated]") { + has_type>(MatcherA() || MatcherB() || MatcherC()); + + REQUIRE_THAT(1, MatcherA() || MatcherB() || MatcherC()); + + has_type>(MatcherA() || MatcherB() || MatcherC() || MatcherD()); + + REQUIRE_THAT(1, MatcherA() || MatcherB() || MatcherC() || MatcherD()); + } + + TEST_CASE("Combining MatchAllOf does not nest", "[matchers][templated]") { + has_type>(MatcherA() && MatcherB() && MatcherC()); + + REQUIRE_THAT(1, MatcherA() && MatcherB() && MatcherC()); + + has_type>(MatcherA() && MatcherB() && MatcherC() && MatcherD()); + + REQUIRE_THAT(1, MatcherA() && MatcherB() && MatcherC() && MatcherD()); + } + + TEST_CASE("Combining MatchNotOf does not nest", "[matchers][templated]") { + has_type>(!MatcherA()); + + REQUIRE_THAT(0, !MatcherA()); + + has_type(!!MatcherA()); + + REQUIRE_THAT(1, !!MatcherA()); + + has_type>(!!!MatcherA()); + + REQUIRE_THAT(0, !!!MatcherA()); + + has_type(!!!!MatcherA()); + + REQUIRE_THAT(1, !!!!MatcherA()); + } + + struct EvilAddressOfOperatorUsed : std::exception { + EvilAddressOfOperatorUsed() {} + const char* what() const noexcept override { + return "overloaded address-of operator of matcher was used instead of std::addressof"; + } + }; + + struct EvilCommaOperatorUsed : std::exception { + EvilCommaOperatorUsed() {} + const char* what() const noexcept override { + return "overloaded comma operator of matcher was used"; + } + }; + + struct EvilMatcher : Catch::MatcherBaseGeneric { + std::string describe() const { + return "equals: 45"; + } + + bool match(int i) const { + return i == 45; + } + + EvilMatcher const* operator& () const { + throw EvilAddressOfOperatorUsed(); + } + + int operator,(EvilMatcher const&) const { + throw EvilCommaOperatorUsed(); + } + }; + + TEST_CASE("Overloaded comma or address-of operators are not used", "[matchers][templated]") { + REQUIRE_THROWS_AS((EvilMatcher(), EvilMatcher()), EvilCommaOperatorUsed); + REQUIRE_THROWS_AS(&EvilMatcher(), EvilAddressOfOperatorUsed); + REQUIRE_NOTHROW(EvilMatcher() || EvilMatcher() && !EvilMatcher()); + REQUIRE_NOTHROW((EvilMatcher() && EvilMatcher()) || !EvilMatcher()); + } + + struct ImmovableMatcher : Catch::MatcherBaseGeneric { + ImmovableMatcher() = default; + ImmovableMatcher(ImmovableMatcher const&) = delete; + ImmovableMatcher(ImmovableMatcher &&) = delete; + ImmovableMatcher& operator=(ImmovableMatcher const&) = delete; + ImmovableMatcher& operator=(ImmovableMatcher &&) = delete; + + std::string describe() const { + return "always false"; + } + + template + bool match(T&&) const { + return false; + } + }; + + struct MatcherWasMovedOrCopied : std::exception { + MatcherWasMovedOrCopied() {} + const char* what() const noexcept override { + return "attempted to copy or move a matcher"; + } + }; + + struct ThrowOnCopyOrMoveMatcher : Catch::MatcherBaseGeneric { + ThrowOnCopyOrMoveMatcher() = default; + ThrowOnCopyOrMoveMatcher(ThrowOnCopyOrMoveMatcher const&) { + throw MatcherWasMovedOrCopied(); + } + ThrowOnCopyOrMoveMatcher(ThrowOnCopyOrMoveMatcher &&) { + throw MatcherWasMovedOrCopied(); + } + ThrowOnCopyOrMoveMatcher& operator=(ThrowOnCopyOrMoveMatcher const&) { + throw MatcherWasMovedOrCopied(); + } + ThrowOnCopyOrMoveMatcher& operator=(ThrowOnCopyOrMoveMatcher &&) { + throw MatcherWasMovedOrCopied(); + } + + std::string describe() const { + return "always false"; + } + + template + bool match(T&&) const { + return false; + } + }; + + TEST_CASE("Matchers are not moved or copied", "[matchers][templated]") { + REQUIRE_NOTHROW((ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher()); + } + + TEST_CASE("Immovable matchers can be used", "[matchers][templated]") { + REQUIRE_THAT(123, (ImmovableMatcher() && ImmovableMatcher()) || !ImmovableMatcher()); + } + +} +} // namespace MatchersTests #ifdef __clang__ #pragma clang diagnostic pop diff --git a/tests/SelfTest/UsageTests/MatchersBenchmark.tests.cpp b/tests/SelfTest/UsageTests/MatchersBenchmark.tests.cpp new file mode 100644 index 0000000000..dde80d8529 --- /dev/null +++ b/tests/SelfTest/UsageTests/MatchersBenchmark.tests.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include +// #include + +#include + +template +struct EqualsRangeMatcher : Catch::MatcherBaseGeneric> { + + EqualsRangeMatcher(Range const& range) : range{ range } {} + + template + bool match(OtherRange const& other) const { + using std::begin; + using std::end; + + return std::equal(begin(range), end(range), begin(other), end(other)); + } + + std::string describe() const { + return "Equals: " + Catch::rangeToString(range); + } + +private: + Range const& range; +}; + +template +auto EqualsRange(const Range& range) -> EqualsRangeMatcher { + return EqualsRangeMatcher{range}; +} + +// TEST_CASE("Combining templated matchers", "[matchers][templated]") { +// std::array container{{ 1,2,3 }}; + +// std::array a{{ 1,2,3 }}; +// std::vector b{ 0,1,2 }; +// std::list c{ 4,5,6 }; + +// for(int i = 0; i < 10000000; ++i) { +// REQUIRE_THAT(container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c)); +// } +// } + +TEST_CASE("Combining templated and concrete matchers", "[matchers][templated]") { + const std::string str = "foobar"; + const std::array arr{{ 'f', 'o', 'o', 'b', 'a', 'r' }}; + const std::array bad_arr{{ 'o', 'o', 'f', 'b', 'a', 'r' }}; + + using Catch::Matchers::StartsWith; + using Catch::Matchers::EndsWith; + + for(int i = 0; i < 10000000; ++i) { + REQUIRE_THAT(str, StartsWith("foo") && EqualsRange(arr) && EndsWith("bar")); + REQUIRE_THAT(str, StartsWith("foo") && !EqualsRange(bad_arr) && EndsWith("bar")); + } +} \ No newline at end of file