From 3a3efebd163f19dc8b9d8bbed4ab5309fc752867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ho=C5=99e=C5=88ovsk=C3=BD?= Date: Mon, 9 Mar 2020 11:13:07 +0100 Subject: [PATCH] Add IsEmpty and SizeIs matchers for ranges/containers `SizeIs` can accept both `size_t` and a matcher. In the first case, it checks whether the size of the range is equal to specified size. In the second case, it checks whether the provided matcher accepts the size of the range. --- docs/release-notes.md | 4 +- src/CMakeLists.txt | 5 +- .../internal/catch_container_nonmembers.hpp | 67 +++++++ .../catch_matchers_container_properties.cpp | 30 ++++ .../catch_matchers_container_properties.hpp | 86 +++++++++ .../Baselines/automake.sw.approved.txt | 2 + .../Baselines/compact.sw.approved.txt | 17 ++ .../Baselines/console.std.approved.txt | 4 +- .../Baselines/console.sw.approved.txt | 131 +++++++++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 8 +- .../Baselines/sonarqube.sw.approved.txt | 6 + tests/SelfTest/Baselines/tap.sw.approved.txt | 36 +++- .../Baselines/teamcity.sw.approved.txt | 4 + tests/SelfTest/Baselines/xml.sw.approved.txt | 164 +++++++++++++++++- .../UsageTests/MatchersRanges.tests.cpp | 112 ++++++++++++ 15 files changed, 666 insertions(+), 10 deletions(-) create mode 100644 src/catch2/internal/catch_container_nonmembers.hpp create mode 100644 src/catch2/matchers/catch_matchers_container_properties.cpp create mode 100644 src/catch2/matchers/catch_matchers_container_properties.hpp diff --git a/docs/release-notes.md b/docs/release-notes.md index 5e13f6e2..415ab2e1 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -58,10 +58,12 @@ * This does not change `TEST_CASE` and friends in any way * `IStreamingReporter::IsMulti` member function was removed * This is _very_ unlikely to actually affect anyone, as it was default-implemented in the interface, and only used internally +* Various classes not designed for user-extension have been made final + * `ListeningReporter` is now `final` + * Concrete Matchers (e.g. `UnorderedEquals` vector matcher) are now `final` * `ListeningReporter` is now final - ### Improvements * Matchers have been extended with the ability to use different signatures of `match` (#1307, #1553, #1554, #1843) * This includes having templated `match` member function diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 82c9b6a5..3a7c4821 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,13 +33,14 @@ set(BENCHMARK_SOURCES SOURCE_GROUP("benchmark" FILES ${BENCHMARK_HEADERS} ${BENCHMARK_SOURCES}) set(INTERNAL_HEADERS + ${SOURCES_DIR}/internal/catch_capture_matchers.h + ${SOURCES_DIR}/internal/catch_container_nonmembers.hpp ${SOURCES_DIR}/catch.hpp ${SOURCES_DIR}/catch_approx.h ${SOURCES_DIR}/catch_assertionhandler.h ${SOURCES_DIR}/catch_assertioninfo.h ${SOURCES_DIR}/catch_assertionresult.h ${SOURCES_DIR}/catch_capture.hpp - ${SOURCES_DIR}/internal/catch_capture_matchers.h ${SOURCES_DIR}/catch_clara.h ${SOURCES_DIR}/catch_commandline.h ${SOURCES_DIR}/catch_common.h @@ -73,6 +74,7 @@ set(INTERNAL_HEADERS ${SOURCES_DIR}/catch_leak_detector.h ${SOURCES_DIR}/catch_list.h ${SOURCES_DIR}/matchers/catch_matchers.hpp + ${SOURCES_DIR}/matchers/catch_matchers_container_properties.hpp ${SOURCES_DIR}/matchers/catch_matchers_contains.hpp ${SOURCES_DIR}/matchers/catch_matchers_exception.hpp ${SOURCES_DIR}/matchers/catch_matchers_floating.hpp @@ -157,6 +159,7 @@ set(IMPL_SOURCES ${SOURCES_DIR}/catch_list.cpp ${SOURCES_DIR}/catch_leak_detector.cpp ${SOURCES_DIR}/matchers/catch_matchers.cpp + ${SOURCES_DIR}/matchers/catch_matchers_container_properties.cpp ${SOURCES_DIR}/matchers/catch_matchers_exception.cpp ${SOURCES_DIR}/matchers/catch_matchers_floating.cpp ${SOURCES_DIR}/matchers/catch_matchers_generic.cpp diff --git a/src/catch2/internal/catch_container_nonmembers.hpp b/src/catch2/internal/catch_container_nonmembers.hpp new file mode 100644 index 00000000..781b3dc4 --- /dev/null +++ b/src/catch2/internal/catch_container_nonmembers.hpp @@ -0,0 +1,67 @@ +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TWOBLUECUBES_CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED + +#include + + +// We want a simple polyfill over `std::empty`, `std::size` and so on +// for C++14 or C++ libraries with incomplete support. +// We also have to handle that MSVC std lib will happily provide these +// under older standards. +#if defined(CATCH_CPP17_OR_GREATER) || defined(_MSC_VER) + +// We are already using this header either way, so there shouldn't +// be much additional overhead in including it to get the feature +// test macros +#include + +# if !defined(__cpp_lib_nonmember_container_access) +# define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS +# endif + +#else +#define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS +#endif + + + +namespace Catch { +namespace Detail { + +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + template + constexpr auto empty(Container const& cont) -> decltype(cont.empty()) { + return cont.empty(); + } + template + constexpr bool empty(const T (&)[N]) noexcept { + // GCC < 7 does not support the const T(&)[] parameter syntax + // so we have to ignore the length explicitly + (void)N; + return false; + } + template + constexpr bool empty(std::initializer_list list) noexcept { + return list.size() > 0; + } + + + template + constexpr auto size(Container const& cont) -> decltype(cont.size()) { + return cont.size(); + } + template + constexpr std::size_t size(const T(&)[N]) noexcept { + return N; + } +#endif // CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS + +} // end namespace Detail +} // end namespace Catch + + + +#endif // TWOBLUECUBES_CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED diff --git a/src/catch2/matchers/catch_matchers_container_properties.cpp b/src/catch2/matchers/catch_matchers_container_properties.cpp new file mode 100644 index 00000000..d4da71bf --- /dev/null +++ b/src/catch2/matchers/catch_matchers_container_properties.cpp @@ -0,0 +1,30 @@ +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +#include + +namespace Catch { +namespace Matchers { + + std::string IsEmptyMatcher::describe() const { + return "is empty"; + } + + std::string HasSizeMatcher::describe() const { + ReusableStringStream sstr; + sstr << "has size == " << m_target_size; + return sstr.str(); + } + + IsEmptyMatcher IsEmpty() { + return {}; + } + + HasSizeMatcher SizeIs(std::size_t sz) { + return HasSizeMatcher{ sz }; + } + +} // end namespace Matchers +} // end namespace Catch diff --git a/src/catch2/matchers/catch_matchers_container_properties.hpp b/src/catch2/matchers/catch_matchers_container_properties.hpp new file mode 100644 index 00000000..e2d49ffd --- /dev/null +++ b/src/catch2/matchers/catch_matchers_container_properties.hpp @@ -0,0 +1,86 @@ +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef TWOBLUECUBES_CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED +#define TWOBLUECUBES_CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED + +#include +#include + +namespace Catch { + namespace Matchers { + + class IsEmptyMatcher final : public MatcherGenericBase { + public: + // todo: Use polyfills + template + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::empty; +#else + using std::empty; +#endif + return empty(rng); + } + + std::string describe() const override; + }; + + class HasSizeMatcher final : public MatcherGenericBase { + std::size_t m_target_size; + public: + explicit HasSizeMatcher(std::size_t target_size): + m_target_size(target_size) + {} + + template + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return size(rng) == m_target_size; + } + + std::string describe() const override; + }; + + template + class SizeMatchesMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + explicit SizeMatchesMatcher(Matcher m): + m_matcher(std::move(m)) + {} + + template + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return m_matcher.match(size(rng)); + } + + std::string describe() const override { + return "size matches " + m_matcher.describe(); + } + }; + + + //! Creates a matcher that accepts empty ranges/containers + IsEmptyMatcher IsEmpty(); + //! Creates a matcher that accepts ranges/containers with specific size + HasSizeMatcher SizeIs(std::size_t sz); + template + std::enable_if_t::value, + SizeMatchesMatcher> SizeIs(Matcher&& m) { + return SizeMatchesMatcher{std::forward(m)}; + } + + } // end namespace Matchers +} // end namespace Catch + +#endif // TWOBLUECUBES_CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED diff --git a/tests/SelfTest/Baselines/automake.sw.approved.txt b/tests/SelfTest/Baselines/automake.sw.approved.txt index aee52e51..e67937c7 100644 --- a/tests/SelfTest/Baselines/automake.sw.approved.txt +++ b/tests/SelfTest/Baselines/automake.sw.approved.txt @@ -81,6 +81,7 @@ Nor would this :test-result: PASS Arbitrary predicate matcher :test-result: PASS Assertions then sections :test-result: PASS Basic use of the Contains range matcher +:test-result: PASS Basic use of the Empty range matcher :test-result: PASS CAPTURE can deal with complex expressions :test-result: PASS CAPTURE can deal with complex expressions involving commas :test-result: PASS CAPTURE parses string and character constants @@ -218,6 +219,7 @@ Message from section two :test-result: PASS Tracker :test-result: PASS Trim strings :test-result: FAIL Unexpected exceptions can be translated +:test-result: PASS Usage of the SizeIs range matcher :test-result: PASS Use a custom approx :test-result: PASS Variadic macros :test-result: PASS Vector Approx matcher diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index 74bf50b7..8ae9d726 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -256,6 +256,14 @@ MatchersRanges.tests.cpp:: passed: in, !Contains(8) for: { 1, 2, 3, MatchersRanges.tests.cpp:: passed: in, Contains(MoveOnlyTestElement{ 2 }) for: { 1, 2, 3 } contains element 2 MatchersRanges.tests.cpp:: passed: in, !Contains(MoveOnlyTestElement{ 9 }) for: { 1, 2, 3 } not contains element 9 MatchersRanges.tests.cpp:: passed: in, Contains(Catch::Matchers::WithinAbs(0.5, 0.5)) for: { 1.0, 2.0, 3.0, 0.0 } contains element matching is within 0.5 of 0.5 +MatchersRanges.tests.cpp:: passed: empty_array, IsEmpty() for: { } is empty +MatchersRanges.tests.cpp:: passed: non_empty_array, !IsEmpty() for: { 0.0 } not is empty +MatchersRanges.tests.cpp:: passed: empty_vec, IsEmpty() for: { } is empty +MatchersRanges.tests.cpp:: passed: non_empty_vec, !IsEmpty() for: { 'a', 'b', 'c' } not is empty +MatchersRanges.tests.cpp:: passed: inner_lists_are_empty, !IsEmpty() for: { { } } not is empty +MatchersRanges.tests.cpp:: passed: inner_lists_are_empty.front(), IsEmpty() for: { } is empty +MatchersRanges.tests.cpp:: passed: has_empty{}, !IsEmpty() for: {?} not is empty +MatchersRanges.tests.cpp:: passed: unrelated::ADL_empty{}, IsEmpty() for: {?} is empty Message.tests.cpp:: passed: with 7 messages: 'a := 1' and 'b := 2' and 'c := 3' and 'a + b := 3' and 'a+b := 3' and 'c > b := true' and 'a == 1 := true' Message.tests.cpp:: passed: with 7 messages: 'std::vector{1, 2, 3}[0, 1, 2] := 3' and 'std::vector{1, 2, 3}[(0, 1)] := 2' and 'std::vector{1, 2, 3}[0] := 1' and '(helper_1436{12, -12}) := { 12, -12 }' and '(helper_1436(-12, 12)) := { -12, 12 }' and '(1, 2) := 2' and '(2, 3) := 3' Message.tests.cpp:: passed: with 11 messages: '("comma, in string", "escaped, \", ") := "escaped, ", "' and '"single quote in string,'," := "single quote in string,',"' and '"some escapes, \\,\\\\" := "some escapes, \,\\"' and '"some, ), unmatched, } prenheses {[<" := "some, ), unmatched, } prenheses {[<"' and ''"' := '"'' and ''\'' := '''' and '',' := ','' and ''}' := '}'' and '')' := ')'' and ''(' := '('' and ''{' := '{'' @@ -1563,6 +1571,15 @@ StringManip.tests.cpp:: passed: trim(StringRef(whitespace_at_both_e == There is no extra whitespace here Exception.tests.cpp:: failed: unexpected exception with message: '3.14' +MatchersRanges.tests.cpp:: passed: empty_vec, SizeIs(0) for: { } has size == 0 +MatchersRanges.tests.cpp:: passed: empty_vec, !SizeIs(2) for: { } not has size == 2 +MatchersRanges.tests.cpp:: passed: empty_vec, SizeIs(Lt(2)) for: { } size matches is less than 2 +MatchersRanges.tests.cpp:: passed: arr, SizeIs(2) for: { 0, 0 } has size == 2 +MatchersRanges.tests.cpp:: passed: arr, SizeIs( Lt(3)) for: { 0, 0 } size matches is less than 3 +MatchersRanges.tests.cpp:: passed: arr, !SizeIs(!Lt(3)) for: { 0, 0 } not size matches not is less than 3 +MatchersRanges.tests.cpp:: passed: map, SizeIs(3) for: { {?}, {?}, {?} } has size == 3 +MatchersRanges.tests.cpp:: passed: unrelated::ADL_size{}, SizeIs(12) for: {?} has size == 12 +MatchersRanges.tests.cpp:: passed: has_size{}, SizeIs(13) for: {?} has size == 13 Approx.tests.cpp:: passed: d == approx( 1.23 ) for: 1.23 == Approx( 1.23 ) Approx.tests.cpp:: passed: d == approx( 1.22 ) for: 1.23 == Approx( 1.22 ) Approx.tests.cpp:: passed: d == approx( 1.24 ) for: 1.23 == Approx( 1.24 ) diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 339d9b0d..ab80cb77 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: 329 | 255 passed | 70 failed | 4 failed as expected -assertions: 1851 | 1699 passed | 131 failed | 21 failed as expected +test cases: 331 | 257 passed | 70 failed | 4 failed as expected +assertions: 1868 | 1716 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 ad53af5d..0df71623 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -2050,6 +2050,67 @@ MatchersRanges.tests.cpp:: PASSED: with expansion: { 1.0, 2.0, 3.0, 0.0 } contains element matching is within 0.5 of 0.5 +------------------------------------------------------------------------------- +Basic use of the Empty range matcher + Simple, std-provided containers +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( empty_array, IsEmpty() ) +with expansion: + { } is empty + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( non_empty_array, !IsEmpty() ) +with expansion: + { 0.0 } not is empty + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( empty_vec, IsEmpty() ) +with expansion: + { } is empty + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( non_empty_vec, !IsEmpty() ) +with expansion: + { 'a', 'b', 'c' } not is empty + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( inner_lists_are_empty, !IsEmpty() ) +with expansion: + { { } } not is empty + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( inner_lists_are_empty.front(), IsEmpty() ) +with expansion: + { } is empty + +------------------------------------------------------------------------------- +Basic use of the Empty range matcher + Type with empty +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( has_empty{}, !IsEmpty() ) +with expansion: + {?} not is empty + +------------------------------------------------------------------------------- +Basic use of the Empty range matcher + Type requires ADL found empty free function +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( unrelated::ADL_empty{}, IsEmpty() ) +with expansion: + {?} is empty + ------------------------------------------------------------------------------- CAPTURE can deal with complex expressions ------------------------------------------------------------------------------- @@ -11379,6 +11440,72 @@ Exception.tests.cpp:: FAILED: due to unexpected exception with message: 3.14 +------------------------------------------------------------------------------- +Usage of the SizeIs range matcher + Some with stdlib containers +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( empty_vec, SizeIs(0) ) +with expansion: + { } has size == 0 + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( empty_vec, !SizeIs(2) ) +with expansion: + { } not has size == 2 + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( empty_vec, SizeIs(Lt(2)) ) +with expansion: + { } size matches is less than 2 + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( arr, SizeIs(2) ) +with expansion: + { 0, 0 } has size == 2 + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( arr, SizeIs( Lt(3)) ) +with expansion: + { 0, 0 } size matches is less than 3 + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( arr, !SizeIs(!Lt(3)) ) +with expansion: + { 0, 0 } not size matches not is less than 3 + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( map, SizeIs(3) ) +with expansion: + { {?}, {?}, {?} } has size == 3 + +------------------------------------------------------------------------------- +Usage of the SizeIs range matcher + Type requires ADL found size free function +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( unrelated::ADL_size{}, SizeIs(12) ) +with expansion: + {?} has size == 12 + +------------------------------------------------------------------------------- +Usage of the SizeIs range matcher + Type has size member +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + REQUIRE_THAT( has_size{}, SizeIs(13) ) +with expansion: + {?} has size == 13 + ------------------------------------------------------------------------------- Use a custom approx ------------------------------------------------------------------------------- @@ -14498,6 +14625,6 @@ Misc.tests.cpp: Misc.tests.cpp:: PASSED: =============================================================================== -test cases: 329 | 239 passed | 86 failed | 4 failed as expected -assertions: 1868 | 1699 passed | 148 failed | 21 failed as expected +test cases: 331 | 241 passed | 86 failed | 4 failed as expected +assertions: 1885 | 1716 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 c1dedc83..08e14692 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -348,6 +348,9 @@ Exception.tests.cpp: + + + @@ -1249,6 +1252,9 @@ FAILED: Exception.tests.cpp: + + + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index b89ec533..c50a6876 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -1222,6 +1222,12 @@ Matchers.tests.cpp: + + + + + + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 61876cff..fc186d97 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -510,6 +510,22 @@ ok {test-number} - in, Contains(MoveOnlyTestElement{ 2 }) for: { 1, 2, 3 } conta ok {test-number} - in, !Contains(MoveOnlyTestElement{ 9 }) for: { 1, 2, 3 } not contains element 9 # Basic use of the Contains range matcher ok {test-number} - in, Contains(Catch::Matchers::WithinAbs(0.5, 0.5)) for: { 1.0, 2.0, 3.0, 0.0 } contains element matching is within 0.5 of 0.5 +# Basic use of the Empty range matcher +ok {test-number} - empty_array, IsEmpty() for: { } is empty +# Basic use of the Empty range matcher +ok {test-number} - non_empty_array, !IsEmpty() for: { 0.0 } not is empty +# Basic use of the Empty range matcher +ok {test-number} - empty_vec, IsEmpty() for: { } is empty +# Basic use of the Empty range matcher +ok {test-number} - non_empty_vec, !IsEmpty() for: { 'a', 'b', 'c' } not is empty +# Basic use of the Empty range matcher +ok {test-number} - inner_lists_are_empty, !IsEmpty() for: { { } } not is empty +# Basic use of the Empty range matcher +ok {test-number} - inner_lists_are_empty.front(), IsEmpty() for: { } is empty +# Basic use of the Empty range matcher +ok {test-number} - has_empty{}, !IsEmpty() for: {?} not is empty +# Basic use of the Empty range matcher +ok {test-number} - unrelated::ADL_empty{}, IsEmpty() for: {?} is empty # CAPTURE can deal with complex expressions ok {test-number} - with 7 messages: 'a := 1' and 'b := 2' and 'c := 3' and 'a + b := 3' and 'a+b := 3' and 'c > b := true' and 'a == 1 := true' # CAPTURE can deal with complex expressions involving commas @@ -2973,6 +2989,24 @@ ok {test-number} - trim(StringRef(trailing_whitespace)) == StringRef(no_whitespa ok {test-number} - trim(StringRef(whitespace_at_both_ends)) == StringRef(no_whitespace) for: There is no extra whitespace here == There is no extra whitespace here # Unexpected exceptions can be translated not ok {test-number} - unexpected exception with message: '3.14' +# Usage of the SizeIs range matcher +ok {test-number} - empty_vec, SizeIs(0) for: { } has size == 0 +# Usage of the SizeIs range matcher +ok {test-number} - empty_vec, !SizeIs(2) for: { } not has size == 2 +# Usage of the SizeIs range matcher +ok {test-number} - empty_vec, SizeIs(Lt(2)) for: { } size matches is less than 2 +# Usage of the SizeIs range matcher +ok {test-number} - arr, SizeIs(2) for: { 0, 0 } has size == 2 +# Usage of the SizeIs range matcher +ok {test-number} - arr, SizeIs( Lt(3)) for: { 0, 0 } size matches is less than 3 +# Usage of the SizeIs range matcher +ok {test-number} - arr, !SizeIs(!Lt(3)) for: { 0, 0 } not size matches not is less than 3 +# Usage of the SizeIs range matcher +ok {test-number} - map, SizeIs(3) for: { {?}, {?}, {?} } has size == 3 +# Usage of the SizeIs range matcher +ok {test-number} - unrelated::ADL_size{}, SizeIs(12) for: {?} has size == 12 +# Usage of the SizeIs range matcher +ok {test-number} - has_size{}, SizeIs(13) for: {?} has size == 13 # Use a custom approx ok {test-number} - d == approx( 1.23 ) for: 1.23 == Approx( 1.23 ) # Use a custom approx @@ -3728,5 +3762,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..1860 +1..1877 diff --git a/tests/SelfTest/Baselines/teamcity.sw.approved.txt b/tests/SelfTest/Baselines/teamcity.sw.approved.txt index b0566b41..a48b4612 100644 --- a/tests/SelfTest/Baselines/teamcity.sw.approved.txt +++ b/tests/SelfTest/Baselines/teamcity.sw.approved.txt @@ -195,6 +195,8 @@ Exception.tests.cpp:|nunexpected exception with message:|n "unexpe ##teamcity[testFinished name='Assertions then sections' duration="{duration}"] ##teamcity[testStarted name='Basic use of the Contains range matcher'] ##teamcity[testFinished name='Basic use of the Contains range matcher' duration="{duration}"] +##teamcity[testStarted name='Basic use of the Empty range matcher'] +##teamcity[testFinished name='Basic use of the Empty range matcher' duration="{duration}"] ##teamcity[testStarted name='CAPTURE can deal with complex expressions'] ##teamcity[testFinished name='CAPTURE can deal with complex expressions' duration="{duration}"] ##teamcity[testStarted name='CAPTURE can deal with complex expressions involving commas'] @@ -539,6 +541,8 @@ Exception.tests.cpp:|nunexpected exception with message:|n "For so ##teamcity[testStarted name='Unexpected exceptions can be translated'] Exception.tests.cpp:|nunexpected exception with message:|n "3.14"'] ##teamcity[testFinished name='Unexpected exceptions can be translated' duration="{duration}"] +##teamcity[testStarted name='Usage of the SizeIs range matcher'] +##teamcity[testFinished name='Usage of the SizeIs range matcher' duration="{duration}"] ##teamcity[testStarted name='Use a custom approx'] ##teamcity[testFinished name='Use a custom approx' duration="{duration}"] ##teamcity[testStarted name='Variadic macros'] diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 31f16fc0..0a9a0255 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -2295,6 +2295,82 @@ Nor would this + +
+ + + empty_array, IsEmpty() + + + { } is empty + + + + + non_empty_array, !IsEmpty() + + + { 0.0 } not is empty + + + + + empty_vec, IsEmpty() + + + { } is empty + + + + + non_empty_vec, !IsEmpty() + + + { 'a', 'b', 'c' } not is empty + + + + + inner_lists_are_empty, !IsEmpty() + + + { { } } not is empty + + + + + inner_lists_are_empty.front(), IsEmpty() + + + { } is empty + + + +
+
+ + + has_empty{}, !IsEmpty() + + + {?} not is empty + + + +
+
+ + + unrelated::ADL_empty{}, IsEmpty() + + + {?} is empty + + + +
+ +
a := 1 @@ -13749,6 +13825,90 @@ There is no extra whitespace here + +
+ + + empty_vec, SizeIs(0) + + + { } has size == 0 + + + + + empty_vec, !SizeIs(2) + + + { } not has size == 2 + + + + + empty_vec, SizeIs(Lt(2)) + + + { } size matches is less than 2 + + + + + arr, SizeIs(2) + + + { 0, 0 } has size == 2 + + + + + arr, SizeIs( Lt(3)) + + + { 0, 0 } size matches is less than 3 + + + + + arr, !SizeIs(!Lt(3)) + + + { 0, 0 } not size matches not is less than 3 + + + + + map, SizeIs(3) + + + { {?}, {?}, {?} } has size == 3 + + + +
+
+ + + unrelated::ADL_size{}, SizeIs(12) + + + {?} has size == 12 + + + +
+
+ + + has_size{}, SizeIs(13) + + + {?} has size == 13 + + + +
+ +
@@ -17381,7 +17541,7 @@ loose text artifact - + - + diff --git a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp b/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp index 7cf5590b..d958ac86 100644 --- a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp +++ b/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp @@ -2,14 +2,17 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include #include #include #include #include #include +#include #include +namespace { namespace unrelated { class needs_ADL_begin { std::array elements{ {1, 2, 3, 4, 5} }; @@ -27,6 +30,7 @@ namespace unrelated { return rhs.End(); } }; +} // end unrelated namespace } // end anon namespace struct MoveOnlyTestElement { @@ -105,3 +109,111 @@ TEST_CASE("Basic use of the Contains range matcher", "[matchers][templated][cont REQUIRE_THAT(in, Contains(Catch::Matchers::WithinAbs(0.5, 0.5))); } } + +namespace { + + struct has_empty { + bool empty() const { return false; } + }; + +namespace unrelated { + struct ADL_empty { + bool Empty() const { return true; } + + friend bool empty(ADL_empty e) { + return e.Empty(); + } + }; + +} // end namespace unrelated +} // end unnamed namespace + +TEST_CASE("Basic use of the Empty range matcher", "[matchers][templated][empty]") { + using Catch::Matchers::IsEmpty; + SECTION("Simple, std-provided containers") { + std::array empty_array{}; + std::array non_empty_array{}; + REQUIRE_THAT(empty_array, IsEmpty()); + REQUIRE_THAT(non_empty_array, !IsEmpty()); + + std::vector empty_vec; + std::vector non_empty_vec{ 'a', 'b', 'c' }; + REQUIRE_THAT(empty_vec, IsEmpty()); + REQUIRE_THAT(non_empty_vec, !IsEmpty()); + + std::list>> inner_lists_are_empty; + inner_lists_are_empty.push_back({}); + REQUIRE_THAT(inner_lists_are_empty, !IsEmpty()); + REQUIRE_THAT(inner_lists_are_empty.front(), IsEmpty()); + } + SECTION("Type with empty") { + REQUIRE_THAT(has_empty{}, !IsEmpty()); + } + SECTION("Type requires ADL found empty free function") { + REQUIRE_THAT(unrelated::ADL_empty{}, IsEmpty()); + } +} + +namespace { + class LessThanMatcher final : public Catch::Matchers::MatcherBase { + size_t m_target; + public: + explicit LessThanMatcher(size_t target): + m_target(target) + {} + + bool match(size_t const& size) const override { + return size < m_target; + } + + std::string describe() const override { + return "is less than " + std::to_string(m_target); + } + }; + + LessThanMatcher Lt(size_t sz) { + return LessThanMatcher{ sz }; + } + + namespace unrelated { + struct ADL_size { + size_t sz() const { + return 12; + } + friend size_t size(ADL_size s) { + return s.sz(); + } + }; + } // end namespace unrelated + + struct has_size { + size_t size() const { + return 13; + } + }; + +} // end unnamed namespace + +TEST_CASE("Usage of the SizeIs range matcher", "[matchers][templated][size]") { + using Catch::Matchers::SizeIs; + SECTION("Some with stdlib containers") { + std::vector empty_vec; + REQUIRE_THAT(empty_vec, SizeIs(0)); + REQUIRE_THAT(empty_vec, !SizeIs(2)); + REQUIRE_THAT(empty_vec, SizeIs(Lt(2))); + + std::array arr{}; + REQUIRE_THAT(arr, SizeIs(2)); + REQUIRE_THAT(arr, SizeIs( Lt(3))); + REQUIRE_THAT(arr, !SizeIs(!Lt(3))); + + std::map map{ {1, 1}, {2, 2}, {3, 3} }; + REQUIRE_THAT(map, SizeIs(3)); + } + SECTION("Type requires ADL found size free function") { + REQUIRE_THAT(unrelated::ADL_size{}, SizeIs(12)); + } + SECTION("Type has size member") { + REQUIRE_THAT(has_size{}, SizeIs(13)); + } +}