From 17c4b2d0932ba944743b3c1e578352080af8c137 Mon Sep 17 00:00:00 2001 From: melak47 Date: Sun, 16 Feb 2020 11:19:10 +0100 Subject: [PATCH] Feature: generic matchers (#1843) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit extends the Matchers feature with the ability to have type-independent (e.g. templated) matchers. This is done by adding a new base type that Matchers can extend, `MatcherGenericBase`, and overloads of operators `!`, `&&` and `||` that handle matchers extending `MatcherGenericBase` in a special manner. These new matchers can also take their arguments as values and non-const references. Closes #1307 Closes #1553 Closes #1554 Co-authored-by: Martin Hořeňovský --- src/CMakeLists.txt | 2 + src/catch2/catch_capture_matchers.cpp | 2 +- src/catch2/catch_capture_matchers.h | 12 +- src/catch2/catch_matchers_templates.cpp | 32 ++ src/catch2/catch_matchers_templates.hpp | 262 +++++++++++++++ .../Baselines/automake.sw.approved.txt | 8 + .../Baselines/compact.sw.approved.txt | 35 ++ .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 233 +++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 10 +- .../Baselines/sonarqube.sw.approved.txt | 8 + tests/SelfTest/Baselines/tap.sw.approved.txt | 72 ++++- .../Baselines/teamcity.sw.approved.txt | 16 + tests/SelfTest/Baselines/xml.sw.approved.txt | 212 ++++++++++++- tests/SelfTest/UsageTests/Matchers.tests.cpp | 300 +++++++++++++++++- 15 files changed, 1192 insertions(+), 16 deletions(-) create mode 100644 src/catch2/catch_matchers_templates.cpp create mode 100644 src/catch2/catch_matchers_templates.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3e5b53dd9..9e5db7469d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,7 @@ set(INTERNAL_HEADERS ${SOURCES_DIR}/catch_matchers_floating.h ${SOURCES_DIR}/catch_matchers_generic.hpp ${SOURCES_DIR}/catch_matchers_string.h + ${SOURCES_DIR}/catch_matchers_templates.hpp ${SOURCES_DIR}/catch_matchers_vector.h ${SOURCES_DIR}/catch_message.h ${SOURCES_DIR}/catch_meta.hpp @@ -159,6 +160,7 @@ set(IMPL_SOURCES ${SOURCES_DIR}/catch_matchers_floating.cpp ${SOURCES_DIR}/catch_matchers_generic.cpp ${SOURCES_DIR}/catch_matchers_string.cpp + ${SOURCES_DIR}/catch_matchers_templates.cpp ${SOURCES_DIR}/catch_message.cpp ${SOURCES_DIR}/catch_output_redirect.cpp ${SOURCES_DIR}/catch_registry_hub.cpp diff --git a/src/catch2/catch_capture_matchers.cpp b/src/catch2/catch_capture_matchers.cpp index ad51baca45..7ce35ef65a 100644 --- a/src/catch2/catch_capture_matchers.cpp +++ b/src/catch2/catch_capture_matchers.cpp @@ -17,7 +17,7 @@ namespace Catch { // the Equals matcher (so the header does not mention matchers) void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ) { std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr expr( exceptionMessage, matcher, matcherString ); + MatchExpr expr( std::move(exceptionMessage), matcher, matcherString ); handler.handleExpr( expr ); } diff --git a/src/catch2/catch_capture_matchers.h b/src/catch2/catch_capture_matchers.h index 3f883a3914..7ff23628d7 100644 --- a/src/catch2/catch_capture_matchers.h +++ b/src/catch2/catch_capture_matchers.h @@ -16,13 +16,13 @@ namespace Catch { template class MatchExpr : public ITransientExpression { - ArgT const& m_arg; + ArgT && m_arg; MatcherT m_matcher; StringRef m_matcherString; public: - MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) - : ITransientExpression{ true, matcher.match( arg ) }, - m_arg( arg ), + MatchExpr( ArgT && arg, MatcherT const& matcher, StringRef const& matcherString ) + : ITransientExpression{ true, matcher.match( arg ) }, // not forwarding arg here on purpose + m_arg( std::forward(arg) ), m_matcher( matcher ), m_matcherString( matcherString ) {} @@ -42,8 +42,8 @@ namespace Catch { void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString ); template - auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr { - return MatchExpr( arg, matcher, matcherString ); + auto makeMatchExpr( ArgT && arg, MatcherT const& matcher, StringRef const& matcherString ) -> MatchExpr { + return MatchExpr( std::forward(arg), matcher, matcherString ); } } // namespace Catch diff --git a/src/catch2/catch_matchers_templates.cpp b/src/catch2/catch_matchers_templates.cpp new file mode 100644 index 0000000000..16b4a6853b --- /dev/null +++ b/src/catch2/catch_matchers_templates.cpp @@ -0,0 +1,32 @@ +#include + +namespace Catch { +namespace Matchers { + namespace Impl { + MatcherGenericBase::~MatcherGenericBase() {} + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end) { + std::string description; + std::size_t combined_size = 4; + for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + combined_size += desc->size(); + } + combined_size += (descriptions_end - descriptions_begin - 1) * combine.size(); + + description.reserve(combined_size); + + description += "( "; + bool first = true; + for( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + if( first ) + first = false; + else + description += combine; + description += *desc; + } + description += " )"; + return description; + } + } +} // namespace Matchers +} // namespace Catch diff --git a/src/catch2/catch_matchers_templates.hpp b/src/catch2/catch_matchers_templates.hpp new file mode 100644 index 0000000000..e1387db6ab --- /dev/null +++ b/src/catch2/catch_matchers_templates.hpp @@ -0,0 +1,262 @@ +#ifndef TWOBLUECUBES_CATCH_MATCHERS_TEMPLATES_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_MATCHERS_TEMPLATES_HPP_INCLUDED + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Catch { +namespace Matchers { + namespace Impl { + + template struct MatchAllOfGeneric; + template struct MatchAnyOfGeneric; + template struct MatchNotOfGeneric; + + struct MatcherGenericBase : MatcherUntypedBase { + virtual ~MatcherGenericBase(); + }; + + template + 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); + return arr; + } + + template + std::array array_cat(std::array && lhs, void const* rhs) { + std::array arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + arr[N] = rhs; + return arr; + } + + template + 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; + } + + #ifdef CATCH_CPP17_OR_GREATER + + using std::conjunction; + + #else // CATCH_CPP17_OR_GREATER + + template + struct conjunction : std::true_type {}; + + template + struct conjunction : std::integral_constant::value> {}; + + #endif // CATCH_CPP17_OR_GREATER + + template + using is_generic_matcher = std::is_base_of< + Catch::Matchers::Impl::MatcherGenericBase, + typename std::remove_cv::type>::type + >; + + template + using are_generic_matchers = conjunction...>; + + template + using is_matcher = std::is_base_of< + Catch::Matchers::Impl::MatcherUntypedBase, + typename std::remove_cv::type>::type + >; + + + template + 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) { + 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<>) { + return false; + } + + template + 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{}); + } + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end); + + template + std::string describe_multi_matcher(StringRef combine, std::array const& matchers, std::index_sequence) { + std::array descriptions {{ + static_cast(matchers[Idx])->toString()... + }}; + + return describe_multi_matcher(combine, descriptions.data(), descriptions.data() + descriptions.size()); + } + + + template + struct MatchAllOfGeneric : MatcherGenericBase { + MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{std::addressof(matchers)...} {} + explicit MatchAllOfGeneric(std::array matchers) : m_matchers{matchers} {} + + template + bool match(Arg&& arg) const { + return match_all_of(arg, m_matchers, std::index_sequence_for{}); + } + + std::string describe() const override { + return describe_multi_matcher(" and "_sr, m_matchers, std::index_sequence_for{}); + } + + std::array m_matchers; + }; + + + template + struct MatchAnyOfGeneric : MatcherGenericBase { + MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{std::addressof(matchers)...} {} + explicit MatchAnyOfGeneric(std::array matchers) : m_matchers{matchers} {} + + template + bool match(Arg&& arg) const { + return match_any_of(arg, m_matchers, std::index_sequence_for{}); + } + + std::string describe() const override { + return describe_multi_matcher(" or "_sr, m_matchers, std::index_sequence_for{}); + } + + std::array m_matchers; + }; + + + template + struct MatchNotOfGeneric : MatcherGenericBase { + explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {} + + template + bool match(Arg&& arg) const { + return !m_matcher.match(arg); + } + + std::string describe() const override { + return "not " + m_matcher.toString(); + } + + MatcherT const& m_matcher; + }; + + // compose only generic matchers + template + typename std::enable_if::value, MatchAllOfGeneric>::type + operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { + return {lhs, rhs}; + } + + template + typename std::enable_if::value, MatchAnyOfGeneric>::type + operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { + return {lhs, rhs}; + } + + template + typename std::enable_if::value, MatchNotOfGeneric>::type + operator ! (MatcherT const& matcher) { + return MatchNotOfGeneric{matcher}; + } + + + // compose mixed generic and non-generic matchers + template + typename std::enable_if::value, MatchAllOfGeneric>>::type + operator && (MatcherLHS const& lhs, MatcherBase const& rhs) { + return {lhs, rhs}; + } + + template + typename std::enable_if::value, MatchAllOfGeneric, MatcherRHS>>::type + operator && (MatcherBase const& lhs, MatcherRHS const& rhs) { + return {lhs, rhs}; + } + + template + typename std::enable_if::value, MatchAnyOfGeneric>>::type + operator || (MatcherLHS const& lhs, MatcherBase const& rhs) { + return {lhs, rhs}; + } + + template + typename std::enable_if::value, MatchAnyOfGeneric, MatcherRHS>>::type + operator || (MatcherBase const& lhs, MatcherRHS const& rhs) { + return {lhs, rhs}; + } + + + // avoid deep nesting of matcher templates + template + MatchAllOfGeneric + operator && (MatchAllOfGeneric && lhs, MatchAllOfGeneric && rhs) { + return MatchAllOfGeneric{array_cat(std::move(lhs.m_matchers), std::move(rhs.m_matchers))}; + } + + template + typename std::enable_if::value, MatchAllOfGeneric>::type + operator && (MatchAllOfGeneric && lhs, MatcherRHS const& rhs) { + return MatchAllOfGeneric{array_cat(std::move(lhs.m_matchers), static_cast(&rhs))}; + } + + template + typename std::enable_if::value, MatchAllOfGeneric>::type + operator && (MatcherLHS const& lhs, MatchAllOfGeneric && rhs) { + return MatchAllOfGeneric{array_cat(static_cast(std::addressof(lhs)), std::move(rhs.m_matchers))}; + } + + template + MatchAnyOfGeneric + operator || (MatchAnyOfGeneric && lhs, MatchAnyOfGeneric && rhs) { + return MatchAnyOfGeneric{array_cat(std::move(lhs.m_matchers), std::move(rhs.m_matchers))}; + } + + template + typename std::enable_if::value, MatchAnyOfGeneric>::type + operator || (MatchAnyOfGeneric && lhs, MatcherRHS const& rhs) { + return MatchAnyOfGeneric{array_cat(std::move(lhs.m_matchers), static_cast(std::addressof(rhs)))}; + } + + template + typename std::enable_if::value, MatchAnyOfGeneric>::type + operator || (MatcherLHS const& lhs, MatchAnyOfGeneric && rhs) { + return MatchAnyOfGeneric{array_cat(static_cast(std::addressof(lhs)), std::move(rhs.m_matchers))}; + } + + template + MatcherT const& operator ! (MatchNotOfGeneric const& matcher) { + return matcher.m_matcher; + } + + } // namespace Impl + +} // namespace Matchers + +using namespace Matchers; +using Matchers::Impl::MatcherGenericBase; + +} // namespace Catch + +#endif //TWOBLUECUBES_CATCH_MATCHERS_TEMPLATES_HPP_INCLUDED diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index 035a92f2f2..db7f400616 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -85,6 +85,13 @@ 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 MatchAllOfGeneric does not nest +:test-result: PASS Combining MatchAnyOfGeneric does not nest +:test-result: PASS Combining MatchNotOfGeneric does not nest +:test-result: PASS Combining concrete matchers does not use templated matchers +:test-result: PASS Combining only templated matchers +:test-result: PASS Combining templated and concrete matchers +:test-result: PASS Combining templated matchers :test-result: PASS Commas in various macros are allowed :test-result: PASS Comparing function pointers :test-result: PASS Comparison ops @@ -147,6 +154,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 66f757687e..0d6cd16051 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -263,6 +263,37 @@ 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< decltype(MatcherA() && MatcherB() && MatcherC()), Catch::Matchers::Impl::MatchAllOfGeneric >::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< decltype(MatcherA() && MatcherB() && MatcherC() && MatcherD()), Catch::Matchers::Impl::MatchAllOfGeneric >::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< decltype(MatcherA() || MatcherB() || MatcherC()), Catch::Matchers::Impl::MatchAnyOfGeneric >::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< decltype(MatcherA() || MatcherB() || MatcherC() || MatcherD()), Catch::Matchers::Impl::MatchAnyOfGeneric >::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< decltype(!MatcherA()), Catch::Matchers::Impl::MatchNotOfGeneric >::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< decltype(!!MatcherA()), MatcherA const& >::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< decltype(!!!MatcherA()), Catch::Matchers::Impl::MatchNotOfGeneric >::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< decltype(!!!!MatcherA()), MatcherA const & >::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< decltype(StartsWith("foo") || (StartsWith("bar") && EndsWith("bar") && !EndsWith("foo"))), Catch::Matchers::Impl::MatchAnyOf >::value' +Matchers.tests.cpp:: passed: with 1 message: 'std::is_same< decltype(MatcherA() || MatcherB()), Catch::Matchers::Impl::MatchAnyOfGeneric >::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< decltype(MatcherA() && MatcherB()), Catch::Matchers::Impl::MatchAllOfGeneric >::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< decltype(MatcherA() || !MatcherB()), Catch::Matchers::Impl::MatchAnyOfGeneric> >::value' +Matchers.tests.cpp:: passed: 1, MatcherA() || !MatcherB() for: 1 ( equals: (int) 1 or (float) 1.0f or not equals: (long long) 1 ) +Matchers.tests.cpp:: passed: vec, Predicate>([](auto const& vec) { return std::all_of(vec.begin(), vec.end(), [](int elem) { return elem % 2 == 1; }); }, "All elements are odd") && !EqualsRange(a) for: { 1, 3, 5 } ( matches predicate: "All elements are odd" and not Equals: { 5, 3, 1 } ) +Matchers.tests.cpp:: passed: str, StartsWith("foo") && EqualsRange(arr) && EndsWith("bar") for: "foobar" ( starts with: "foo" and Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and ends with: "bar" ) +Matchers.tests.cpp:: passed: str, StartsWith("foo") && !EqualsRange(bad_arr) && EndsWith("bar") for: "foobar" ( starts with: "foo" and not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and ends with: "bar" ) +Matchers.tests.cpp:: passed: str, EqualsRange(arr) && StartsWith("foo") && EndsWith("bar") for: "foobar" ( Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and starts with: "foo" and ends with: "bar" ) +Matchers.tests.cpp:: passed: str, !EqualsRange(bad_arr) && StartsWith("foo") && EndsWith("bar") for: "foobar" ( not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and starts with: "foo" and ends with: "bar" ) +Matchers.tests.cpp:: passed: str, EqualsRange(bad_arr) || (StartsWith("foo") && EndsWith("bar")) for: "foobar" ( Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } or ( starts with: "foo" and ends with: "bar" ) ) +Matchers.tests.cpp:: passed: str, (StartsWith("foo") && EndsWith("bar")) || EqualsRange(bad_arr) for: "foobar" ( ( starts with: "foo" and ends with: "bar" ) or Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } ) +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 } ) 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} @@ -883,6 +914,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 d714a7f438..7056db0b72 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: 320 | 246 passed | 70 failed | 4 failed as expected -assertions: 1792 | 1640 passed | 131 failed | 21 failed as expected +test cases: 328 | 254 passed | 70 failed | 4 failed as expected +assertions: 1827 | 1675 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 e6d9c48dcb..595acde66e 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -2116,6 +2116,217 @@ ToStringGeneral.tests.cpp:: PASSED: with expansion: 5 == 5 +------------------------------------------------------------------------------- +Combining MatchAllOfGeneric does not nest +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same< decltype(MatcherA() && MatcherB() && MatcherC()), Catch:: + Matchers::Impl::MatchAllOfGeneric >::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< decltype(MatcherA() && MatcherB() && MatcherC() && MatcherD()), + Catch::Matchers::Impl::MatchAllOfGeneric >::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 MatchAnyOfGeneric does not nest +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same< decltype(MatcherA() || MatcherB() || MatcherC()), Catch:: + Matchers::Impl::MatchAnyOfGeneric >::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< decltype(MatcherA() || MatcherB() || MatcherC() || MatcherD()), + Catch::Matchers::Impl::MatchAnyOfGeneric >::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 MatchNotOfGeneric does not nest +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same< decltype(!MatcherA()), Catch::Matchers::Impl::MatchNotOfGeneric + >::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< decltype(!!MatcherA()), MatcherA const& >::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< decltype(!!!MatcherA()), Catch::Matchers::Impl:: + MatchNotOfGeneric >::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< decltype(!!!!MatcherA()), MatcherA const & >::value + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( 1, !!!!MatcherA() ) +with expansion: + 1 equals: (int) 1 or (float) 1.0f + +------------------------------------------------------------------------------- +Combining concrete matchers does not use templated matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same< decltype(StartsWith("foo") || (StartsWith("bar") && EndsWith + ("bar") && !EndsWith("foo"))), Catch::Matchers::Impl::MatchAnyOf + >::value + +------------------------------------------------------------------------------- +Combining only templated matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: +with message: + std::is_same< decltype(MatcherA() || MatcherB()), Catch::Matchers::Impl:: + MatchAnyOfGeneric >::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< decltype(MatcherA() && MatcherB()), Catch::Matchers::Impl:: + MatchAllOfGeneric >::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< decltype(MatcherA() || !MatcherB()), Catch::Matchers::Impl:: + MatchAnyOfGeneric> >::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 ) + +------------------------------------------------------------------------------- +Combining templated and concrete matchers +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( vec, Predicate>([](auto const& vec) { return std::all_of(vec.begin(), vec.end(), [](int elem) { return elem % 2 == 1; }); }, "All elements are odd") && !EqualsRange(a) ) +with expansion: + { 1, 3, 5 } ( matches predicate: "All elements are odd" and not Equals: { 5, + 3, 1 } ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( str, StartsWith("foo") && EqualsRange(arr) && EndsWith("bar") ) +with expansion: + "foobar" ( starts with: "foo" and Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } + and ends with: "bar" ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( str, StartsWith("foo") && !EqualsRange(bad_arr) && EndsWith("bar") ) +with expansion: + "foobar" ( starts with: "foo" and not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' + } and ends with: "bar" ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( str, EqualsRange(arr) && StartsWith("foo") && EndsWith("bar") ) +with expansion: + "foobar" ( Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and starts with: "foo" + and ends with: "bar" ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( str, !EqualsRange(bad_arr) && StartsWith("foo") && EndsWith("bar") ) +with expansion: + "foobar" ( not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and starts with: + "foo" and ends with: "bar" ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( str, EqualsRange(bad_arr) || (StartsWith("foo") && EndsWith("bar")) ) +with expansion: + "foobar" ( Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } or ( starts with: "foo" + and ends with: "bar" ) ) + +Matchers.tests.cpp:: PASSED: + REQUIRE_THAT( str, (StartsWith("foo") && EndsWith("bar")) || EqualsRange(bad_arr) ) +with expansion: + "foobar" ( ( starts with: "foo" and ends with: "bar" ) or Equals: { 'o', 'o', + 'f', 'b', 'a', 'r' } ) + +------------------------------------------------------------------------------- +Combining templated 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 + } ) + ------------------------------------------------------------------------------- Commas in various macros are allowed ------------------------------------------------------------------------------- @@ -6414,6 +6625,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 @@ -14053,6 +14282,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 320 | 230 passed | 86 failed | 4 failed as expected -assertions: 1809 | 1640 passed | 148 failed | 21 failed as expected +test cases: 328 | 238 passed | 86 failed | 4 failed as expected +assertions: 1844 | 1675 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 0a3175dccb..f7214ed739 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -350,6 +350,13 @@ Exception.tests.cpp: + + + + + + + @@ -950,6 +957,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 d359b155df..517c5f0b6f 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -915,6 +915,13 @@ Exception.tests.cpp: + + + + + + + FAILED: @@ -1042,6 +1049,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 bc0f3c252f..f6c539618c 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -524,6 +524,68 @@ 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 MatchAllOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(MatcherA() && MatcherB() && MatcherC()), Catch::Matchers::Impl::MatchAllOfGeneric >::value' +# Combining MatchAllOfGeneric 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 MatchAllOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(MatcherA() && MatcherB() && MatcherC() && MatcherD()), Catch::Matchers::Impl::MatchAllOfGeneric >::value' +# Combining MatchAllOfGeneric 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 MatchAnyOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(MatcherA() || MatcherB() || MatcherC()), Catch::Matchers::Impl::MatchAnyOfGeneric >::value' +# Combining MatchAnyOfGeneric 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 MatchAnyOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(MatcherA() || MatcherB() || MatcherC() || MatcherD()), Catch::Matchers::Impl::MatchAnyOfGeneric >::value' +# Combining MatchAnyOfGeneric 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 MatchNotOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(!MatcherA()), Catch::Matchers::Impl::MatchNotOfGeneric >::value' +# Combining MatchNotOfGeneric does not nest +ok {test-number} - 0, !MatcherA() for: 0 not equals: (int) 1 or (float) 1.0f +# Combining MatchNotOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(!!MatcherA()), MatcherA const& >::value' +# Combining MatchNotOfGeneric does not nest +ok {test-number} - 1, !!MatcherA() for: 1 equals: (int) 1 or (float) 1.0f +# Combining MatchNotOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(!!!MatcherA()), Catch::Matchers::Impl::MatchNotOfGeneric >::value' +# Combining MatchNotOfGeneric does not nest +ok {test-number} - 0, !!!MatcherA() for: 0 not equals: (int) 1 or (float) 1.0f +# Combining MatchNotOfGeneric does not nest +ok {test-number} - with 1 message: 'std::is_same< decltype(!!!!MatcherA()), MatcherA const & >::value' +# Combining MatchNotOfGeneric does not nest +ok {test-number} - 1, !!!!MatcherA() for: 1 equals: (int) 1 or (float) 1.0f +# Combining concrete matchers does not use templated matchers +ok {test-number} - with 1 message: 'std::is_same< decltype(StartsWith("foo") || (StartsWith("bar") && EndsWith("bar") && !EndsWith("foo"))), Catch::Matchers::Impl::MatchAnyOf >::value' +# Combining only templated matchers +ok {test-number} - with 1 message: 'std::is_same< decltype(MatcherA() || MatcherB()), Catch::Matchers::Impl::MatchAnyOfGeneric >::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< decltype(MatcherA() && MatcherB()), Catch::Matchers::Impl::MatchAllOfGeneric >::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< decltype(MatcherA() || !MatcherB()), Catch::Matchers::Impl::MatchAnyOfGeneric> >::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 ) +# Combining templated and concrete matchers +ok {test-number} - vec, Predicate>([](auto const& vec) { return std::all_of(vec.begin(), vec.end(), [](int elem) { return elem % 2 == 1; }); }, "All elements are odd") && !EqualsRange(a) for: { 1, 3, 5 } ( matches predicate: "All elements are odd" and not Equals: { 5, 3, 1 } ) +# Combining templated and concrete matchers +ok {test-number} - str, StartsWith("foo") && EqualsRange(arr) && EndsWith("bar") for: "foobar" ( starts with: "foo" and Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and ends with: "bar" ) +# Combining templated and concrete matchers +ok {test-number} - str, StartsWith("foo") && !EqualsRange(bad_arr) && EndsWith("bar") for: "foobar" ( starts with: "foo" and not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and ends with: "bar" ) +# Combining templated and concrete matchers +ok {test-number} - str, EqualsRange(arr) && StartsWith("foo") && EndsWith("bar") for: "foobar" ( Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and starts with: "foo" and ends with: "bar" ) +# Combining templated and concrete matchers +ok {test-number} - str, !EqualsRange(bad_arr) && StartsWith("foo") && EndsWith("bar") for: "foobar" ( not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and starts with: "foo" and ends with: "bar" ) +# Combining templated and concrete matchers +ok {test-number} - str, EqualsRange(bad_arr) || (StartsWith("foo") && EndsWith("bar")) for: "foobar" ( Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } or ( starts with: "foo" and ends with: "bar" ) ) +# Combining templated and concrete matchers +ok {test-number} - str, (StartsWith("foo") && EndsWith("bar")) || EqualsRange(bad_arr) for: "foobar" ( ( starts with: "foo" and ends with: "bar" ) or Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } ) +# Combining templated 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 } ) # Commas in various macros are allowed ok {test-number} - std::vector{constructor_throws{}, constructor_throws{}} # Commas in various macros are allowed @@ -1688,6 +1750,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 @@ -3610,5 +3680,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..1801 +1..1836 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index 76d6218306..dbfb98351c 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -203,6 +203,20 @@ 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 MatchAllOfGeneric does not nest'] +##teamcity[testFinished name='Combining MatchAllOfGeneric does not nest' duration="{duration}"] +##teamcity[testStarted name='Combining MatchAnyOfGeneric does not nest'] +##teamcity[testFinished name='Combining MatchAnyOfGeneric does not nest' duration="{duration}"] +##teamcity[testStarted name='Combining MatchNotOfGeneric does not nest'] +##teamcity[testFinished name='Combining MatchNotOfGeneric does not nest' duration="{duration}"] +##teamcity[testStarted name='Combining concrete matchers does not use templated matchers'] +##teamcity[testFinished name='Combining concrete matchers does not use templated matchers' duration="{duration}"] +##teamcity[testStarted name='Combining only templated matchers'] +##teamcity[testFinished name='Combining only templated matchers' duration="{duration}"] +##teamcity[testStarted name='Combining templated and concrete matchers'] +##teamcity[testFinished name='Combining templated and concrete matchers' duration="{duration}"] +##teamcity[testStarted name='Combining templated matchers'] +##teamcity[testFinished name='Combining 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'] @@ -395,6 +409,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 ca1f10e4f7..6abee4b7e5 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -2417,6 +2417,179 @@ 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 + + + + + + + + + + + 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 ) + + + + + + + + vec, Predicate<std::vector<int>>([](auto const& vec) { return std::all_of(vec.begin(), vec.end(), [](int elem) { return elem % 2 == 1; }); }, "All elements are odd") && !EqualsRange(a) + + + { 1, 3, 5 } ( matches predicate: "All elements are odd" and not Equals: { 5, 3, 1 } ) + + + + + str, StartsWith("foo") && EqualsRange(arr) && EndsWith("bar") + + + "foobar" ( starts with: "foo" and Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and ends with: "bar" ) + + + + + str, StartsWith("foo") && !EqualsRange(bad_arr) && EndsWith("bar") + + + "foobar" ( starts with: "foo" and not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and ends with: "bar" ) + + + + + str, EqualsRange(arr) && StartsWith("foo") && EndsWith("bar") + + + "foobar" ( Equals: { 'f', 'o', 'o', 'b', 'a', 'r' } and starts with: "foo" and ends with: "bar" ) + + + + + str, !EqualsRange(bad_arr) && StartsWith("foo") && EndsWith("bar") + + + "foobar" ( not Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } and starts with: "foo" and ends with: "bar" ) + + + + + str, EqualsRange(bad_arr) || (StartsWith("foo") && EndsWith("bar")) + + + "foobar" ( Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } or ( starts with: "foo" and ends with: "bar" ) ) + + + + + str, (StartsWith("foo") && EndsWith("bar")) || EqualsRange(bad_arr) + + + "foobar" ( ( starts with: "foo" and ends with: "bar" ) or Equals: { 'o', 'o', 'f', 'b', 'a', 'r' } ) + + + + + + + + container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c) + + + { 1, 2, 3 } ( Equals: { 1, 2, 3 } or Equals: { 0, 1, 2 } or Equals: { 4, 5, 6 } ) + + + + @@ -8025,6 +8198,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() + + + +
@@ -16909,7 +17117,7 @@ loose text artifact
- + - + diff --git a/tests/SelfTest/UsageTests/Matchers.tests.cpp b/tests/SelfTest/UsageTests/Matchers.tests.cpp index 0efc922158..e10e98caba 100644 --- a/tests/SelfTest/UsageTests/Matchers.tests.cpp +++ b/tests/SelfTest/UsageTests/Matchers.tests.cpp @@ -9,10 +9,12 @@ #include #include #include +#include -#include #include #include +#include +#include #ifdef __clang__ #pragma clang diagnostic push @@ -554,6 +556,302 @@ namespace { namespace MatchersTests { REQUIRE_THROWS_MATCHES(throwsSpecialException(2), SpecialException, Message("SpecialException::what")); } + + template + struct EqualsRangeMatcher : Catch::MatcherGenericBase { + + 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 override { + 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 }; + + REQUIRE_THAT(container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c)); + } + + TEST_CASE("Combining templated and concrete matchers", "[matchers][templated]") { + using namespace Catch::Matchers; + std::vector vec{ 1, 3, 5 }; + + std::array a{{ 5, 3, 1 }}; + + REQUIRE_THAT(vec, + Predicate>([](auto const& vec) { + return std::all_of(vec.begin(), vec.end(), [](int elem) { + return elem % 2 == 1; + }); + }, "All elements are odd") && + !EqualsRange(a)); + + 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; + + REQUIRE_THAT(str, StartsWith("foo") && EqualsRange(arr) && EndsWith("bar")); + REQUIRE_THAT(str, StartsWith("foo") && !EqualsRange(bad_arr) && EndsWith("bar")); + + REQUIRE_THAT(str, EqualsRange(arr) && StartsWith("foo") && EndsWith("bar")); + REQUIRE_THAT(str, !EqualsRange(bad_arr) && StartsWith("foo") && EndsWith("bar")); + + REQUIRE_THAT(str, EqualsRange(bad_arr) || (StartsWith("foo") && EndsWith("bar"))); + REQUIRE_THAT(str, (StartsWith("foo") && EndsWith("bar")) || EqualsRange(bad_arr)); + } + + TEST_CASE("Combining concrete matchers does not use templated matchers", "[matchers][templated]") { + using Catch::Matchers::StartsWith; + using Catch::Matchers::EndsWith; + + STATIC_REQUIRE(std::is_same< + decltype(StartsWith("foo") || (StartsWith("bar") && EndsWith("bar") && !EndsWith("foo"))), + Catch::Matchers::Impl::MatchAnyOf + >::value); + } + + struct MatcherA : Catch::MatcherGenericBase { + std::string describe() const override { 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::MatcherGenericBase { + std::string describe() const override { return "equals: (long long) 1"; } + bool match(long long l) const { return l == 1ll; } + }; + + struct MatcherC : Catch::MatcherGenericBase { + std::string describe() const override { return "equals: (T) 1"; } + template + bool match(T t) const { return t == T{1}; } + }; + + struct MatcherD : Catch::MatcherGenericBase { + std::string describe() const override { return "equals: true"; } + bool match(bool b) const { return b == true; } + }; + + TEST_CASE("Combining only templated matchers", "[matchers][templated]") { + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() || MatcherB()), + Catch::Matchers::Impl::MatchAnyOfGeneric + >::value); + + REQUIRE_THAT(1, MatcherA() || MatcherB()); + + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() && MatcherB()), + Catch::Matchers::Impl::MatchAllOfGeneric + >::value); + + REQUIRE_THAT(1, MatcherA() && MatcherB()); + + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() || !MatcherB()), + Catch::Matchers::Impl::MatchAnyOfGeneric> + >::value); + + REQUIRE_THAT(1, MatcherA() || !MatcherB()); + } + + TEST_CASE("Combining MatchAnyOfGeneric does not nest", "[matchers][templated]") { + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() || MatcherB() || MatcherC()), + Catch::Matchers::Impl::MatchAnyOfGeneric + >::value); + + REQUIRE_THAT(1, MatcherA() || MatcherB() || MatcherC()); + + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() || MatcherB() || MatcherC() || MatcherD()), + Catch::Matchers::Impl::MatchAnyOfGeneric + >::value); + + REQUIRE_THAT(1, MatcherA() || MatcherB() || MatcherC() || MatcherD()); + } + + TEST_CASE("Combining MatchAllOfGeneric does not nest", "[matchers][templated]") { + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() && MatcherB() && MatcherC()), + Catch::Matchers::Impl::MatchAllOfGeneric + >::value); + + REQUIRE_THAT(1, MatcherA() && MatcherB() && MatcherC()); + + STATIC_REQUIRE(std::is_same< + decltype(MatcherA() && MatcherB() && MatcherC() && MatcherD()), + Catch::Matchers::Impl::MatchAllOfGeneric + >::value); + + REQUIRE_THAT(1, MatcherA() && MatcherB() && MatcherC() && MatcherD()); + } + + TEST_CASE("Combining MatchNotOfGeneric does not nest", "[matchers][templated]") { + STATIC_REQUIRE(std::is_same< + decltype(!MatcherA()), + Catch::Matchers::Impl::MatchNotOfGeneric + >::value); + + REQUIRE_THAT(0, !MatcherA()); + + STATIC_REQUIRE(std::is_same< + decltype(!!MatcherA()), + MatcherA const& + >::value); + + REQUIRE_THAT(1, !!MatcherA()); + + STATIC_REQUIRE(std::is_same< + decltype(!!!MatcherA()), + Catch::Matchers::Impl::MatchNotOfGeneric + >::value); + + REQUIRE_THAT(0, !!!MatcherA()); + + STATIC_REQUIRE(std::is_same< + decltype(!!!!MatcherA()), + MatcherA const & + >::value); + + 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::MatcherGenericBase { + std::string describe() const override { + 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::MatcherGenericBase { + ImmovableMatcher() = default; + ImmovableMatcher(ImmovableMatcher const&) = delete; + ImmovableMatcher(ImmovableMatcher &&) = delete; + ImmovableMatcher& operator=(ImmovableMatcher const&) = delete; + ImmovableMatcher& operator=(ImmovableMatcher &&) = delete; + + std::string describe() const override { + 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::MatcherGenericBase { + ThrowOnCopyOrMoveMatcher() = default; + [[noreturn]] + ThrowOnCopyOrMoveMatcher(ThrowOnCopyOrMoveMatcher const&) { + throw MatcherWasMovedOrCopied(); + } + [[noreturn]] + ThrowOnCopyOrMoveMatcher(ThrowOnCopyOrMoveMatcher &&) { + throw MatcherWasMovedOrCopied(); + } + ThrowOnCopyOrMoveMatcher& operator=(ThrowOnCopyOrMoveMatcher const&) { + throw MatcherWasMovedOrCopied(); + } + ThrowOnCopyOrMoveMatcher& operator=(ThrowOnCopyOrMoveMatcher &&) { + throw MatcherWasMovedOrCopied(); + } + + std::string describe() const override { + return "always false"; + } + + template + bool match(T&&) const { + return false; + } + }; + + TEST_CASE("Matchers are not moved or copied", "[matchers][templated][approvals]") { + REQUIRE_NOTHROW((ThrowOnCopyOrMoveMatcher() && ThrowOnCopyOrMoveMatcher()) || !ThrowOnCopyOrMoveMatcher()); + } + + TEST_CASE("Immovable matchers can be used", "[matchers][templated][approvals]") { + REQUIRE_THAT(123, (ImmovableMatcher() && ImmovableMatcher()) || !ImmovableMatcher()); + } + + struct ReferencingMatcher : Catch::MatcherGenericBase { + std::string describe() const override { + return "takes reference"; + } + bool match(int& i) const { + return i == 22; + } + }; + + TEST_CASE("Matchers can take references", "[matchers][templated][approvals]") { + REQUIRE_THAT(22, ReferencingMatcher{}); + } + } } // namespace MatchersTests #ifdef __clang__