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)); + } +}