Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AllTrue, AnyTrue, NoneTrue generic range matchers #2319

Merged
merged 3 commits into from Jul 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -20,6 +20,7 @@ DerivedData
Build
.idea
.vs
.vscode
cmake-build-*
benchmark-dir
.conan/test_package/build
Expand All @@ -31,3 +32,5 @@ msvc-sln*
# Currently we use Doxygen for dep graphs and the full docs are only slowly
# being filled in, so we definitely do not want git to deal with the docs.
docs/doxygen
*.cache
compile_commands.json
14 changes: 14 additions & 0 deletions docs/matchers.md
Expand Up @@ -258,6 +258,12 @@ definitions to handle generic range-like types. These are:
* `SizeIs(Matcher size_matcher)`
* `Contains(T&& target_element, Comparator = std::equal_to<>{})`
* `Contains(Matcher element_matcher)`
* `AllMatch(Matcher element_matcher)`
* `NoneMatch(Matcher element_matcher)`
* `AnyMatch(Matcher element_matcher)`
* `AllTrue()`
* `NoneTrue()`
* `AnyTrue()`
`IsEmpty` should be self-explanatory. It successfully matches objects
that are empty according to either `std::empty`, or ADL-found `empty`
Expand All @@ -275,6 +281,14 @@ the target element. The other variant is constructed from a matcher,
in which case a range is accepted if any of its elements is accepted
by the provided matcher.
`AllMatch`, `NoneMatch`, and `AnyMatch` match ranges for which either
all, none, or any of the contained elements matches the given matcher,
respectively.
`AllTrue`, `NoneTrue`, and `AnyTrue` match ranges for which either
all, none, or any of the contained elements are `true`, respectively.
It works for ranges of `bool`s and ranges of elements (explicitly)
convertible to `bool`.
## Writing custom matchers (old style)
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -187,6 +187,7 @@ set(IMPL_SOURCES
${SOURCES_DIR}/interfaces/catch_interfaces_reporter.cpp
${SOURCES_DIR}/internal/catch_list.cpp
${SOURCES_DIR}/matchers/catch_matchers_floating_point.cpp
${SOURCES_DIR}/matchers/catch_matchers_quantifiers.cpp
${SOURCES_DIR}/matchers/catch_matchers_string.cpp
${SOURCES_DIR}/matchers/catch_matchers_templated.cpp
${SOURCES_DIR}/catch_message.cpp
Expand Down
24 changes: 24 additions & 0 deletions src/catch2/matchers/catch_matchers_quantifiers.cpp
@@ -0,0 +1,24 @@

// Copyright Catch2 Authors
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)

// SPDX-License-Identifier: BSL-1.0
#include <catch2/matchers/catch_matchers_quantifiers.hpp>

namespace Catch {
namespace Matchers {
std::string AllTrueMatcher::describe() const { return "contains only true"; }

AllTrueMatcher AllTrue() { return AllTrueMatcher{}; }

std::string NoneTrueMatcher::describe() const { return "contains no true"; }

NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; }

std::string AnyTrueMatcher::describe() const { return "contains at least one true"; }

AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; }
} // namespace Matchers
} // namespace Catch
59 changes: 58 additions & 1 deletion src/catch2/matchers/catch_matchers_quantifiers.hpp
Expand Up @@ -85,7 +85,55 @@ namespace Catch {
}
};

// Creates a matcher that checks whether a range contains element matching a matcher
// Matcher for checking that all elements in range are true.
class AllTrueMatcher final : public MatcherGenericBase {
public:
std::string describe() const override;

template <typename RangeLike>
bool match(RangeLike&& rng) const {
for (auto&& elem : rng) {
if (!elem) {
return false;
}
}
return true;
}
};

// Matcher for checking that no element in range is true.
class NoneTrueMatcher final : public MatcherGenericBase {
public:
std::string describe() const override;

template <typename RangeLike>
bool match(RangeLike&& rng) const {
for (auto&& elem : rng) {
if (elem) {
return false;
}
}
return true;
}
};

// Matcher for checking that any element in range is true.
class AnyTrueMatcher final : public MatcherGenericBase {
public:
std::string describe() const override;

template <typename RangeLike>
bool match(RangeLike&& rng) const {
for (auto&& elem : rng) {
if (elem) {
return true;
}
}
return false;
}
};

// Creates a matcher that checks whether all elements in a range match a matcher
template <typename Matcher>
AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) {
return { CATCH_FORWARD(matcher) };
Expand All @@ -102,6 +150,15 @@ namespace Catch {
AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) {
return { CATCH_FORWARD(matcher) };
}

// Creates a matcher that checks whether all elements in a range are true
AllTrueMatcher AllTrue();

// Creates a matcher that checks whether no element in a range is true
NoneTrueMatcher NoneTrue();

// Creates a matcher that checks whether any element in a range is true
AnyTrueMatcher AnyTrue();
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/SelfTest/Baselines/automake.sw.approved.txt
Expand Up @@ -271,8 +271,11 @@ Message from section two
:test-result: FAIL Unexpected exceptions can be translated
:test-result: PASS Upcasting special member functions
:test-result: PASS Usage of AllMatch range matcher
:test-result: PASS Usage of AllTrue range matcher
:test-result: PASS Usage of AnyMatch range matcher
:test-result: PASS Usage of AnyTrue range matcher
:test-result: PASS Usage of NoneMatch range matcher
:test-result: PASS Usage of NoneTrue range matcher
:test-result: PASS Usage of the SizeIs range matcher
:test-result: PASS Use a custom approx
:test-result: PASS Variadic macros
Expand Down
3 changes: 3 additions & 0 deletions tests/SelfTest/Baselines/automake.sw.multi.approved.txt
Expand Up @@ -264,8 +264,11 @@
:test-result: FAIL Unexpected exceptions can be translated
:test-result: PASS Upcasting special member functions
:test-result: PASS Usage of AllMatch range matcher
:test-result: PASS Usage of AllTrue range matcher
:test-result: PASS Usage of AnyMatch range matcher
:test-result: PASS Usage of AnyTrue range matcher
:test-result: PASS Usage of NoneMatch range matcher
:test-result: PASS Usage of NoneTrue range matcher
:test-result: PASS Usage of the SizeIs range matcher
:test-result: PASS Use a custom approx
:test-result: PASS Variadic macros
Expand Down
57 changes: 57 additions & 0 deletions tests/SelfTest/Baselines/compact.sw.approved.txt
Expand Up @@ -2013,6 +2013,25 @@ MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[3]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[4]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: data, AllTrue() for: { true, true, true, true, true } contains only true
MatchersRanges.tests.cpp:<line number>: passed: data, AllTrue() for: { } contains only true
MatchersRanges.tests.cpp:<line number>: passed: data, !AllTrue() for: { true, true, false, true, true } not contains only true
MatchersRanges.tests.cpp:<line number>: passed: data, !AllTrue() for: { false, false, false, false, false } not contains only true
MatchersRanges.tests.cpp:<line number>: passed: data, AllTrue() for: { true, true, true, true, true } contains only true
MatchersRanges.tests.cpp:<line number>: passed: data, !AllTrue() for: { true, true, false, true, true } not contains only true
MatchersRanges.tests.cpp:<line number>: passed: data, !AllTrue() for: { false, false, false, false, false } not contains only true
MatchersRanges.tests.cpp:<line number>: passed: mocked, AllTrue() for: { true, true, true, true, true } contains only true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[0] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[3] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[4] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked, !AllTrue() for: { true, true, false, true, true } not contains only true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[0] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[3]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[4]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: data, AnyMatch(SizeIs(5)) for: { { 0, 1, 2, 3, 5 }, { 4, -3, -2, 5, 0 }, { 0, 0, 0, 5, 0 }, { 0, -5, 0, 5, 0 }, { 1, 0, 0, -1, 5 } } any match has size == 5
MatchersRanges.tests.cpp:<line number>: passed: data, !AnyMatch(Contains(0) && Contains(10)) for: { { 0, 1, 2, 3, 5 }, { 4, -3, -2, 5, 0 }, { 0, 0, 0, 5, 0 }, { 0, -5, 0, 5, 0 }, { 1, 0, 0, -1, 5 } } not any match ( contains element 0 and contains element 10 )
MatchersRanges.tests.cpp:<line number>: passed: needs_adl, AnyMatch( Predicate<int>( []( int elem ) { return elem < 3; } ) ) for: { 1, 2, 3, 4, 5 } any match matches undescribed predicate
Expand All @@ -2028,6 +2047,25 @@ MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[1]) for: !fal
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[2]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[3]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[4]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: data, AnyTrue() for: { true, true, true, true, true } contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: data, !AnyTrue() for: { } not contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: data, AnyTrue() for: { false, false, true, false, false } contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: data, !AnyTrue() for: { false, false, false, false, false } not contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: data, AnyTrue() for: { true, true, true, true, true } contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: data, AnyTrue() for: { false, false, true, false, false } contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: data, !AnyTrue() for: { false, false, false, false, false } not contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: mocked, AnyTrue() for: { false, false, false, false, true } contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[0] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[3] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[4] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked, AnyTrue() for: { false, false, true, true, true } contains at least one true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[0] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[3]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[4]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: data, NoneMatch(SizeIs(6)) for: { { 0, 1, 2, 3, 5 }, { 4, -3, -2, 5, 0 }, { 0, 0, 0, 5, 0 }, { 0, -5, 0, 5, 0 }, { 1, 0, 0, -1, 5 } } none match has size == 6
MatchersRanges.tests.cpp:<line number>: passed: data, !NoneMatch(Contains(0) && Contains(1)) for: { { 0, 1, 2, 3, 5 }, { 4, -3, -2, 5, 0 }, { 0, 0, 0, 5, 0 }, { 0, -5, 0, 5, 0 }, { 1, 0, 0, -1, 5 } } not none match ( contains element 0 and contains element 1 )
MatchersRanges.tests.cpp:<line number>: passed: needs_adl, NoneMatch( Predicate<int>( []( int elem ) { return elem > 6; } ) ) for: { 1, 2, 3, 4, 5 } none match matches undescribed predicate
Expand All @@ -2043,6 +2081,25 @@ MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[1]) for: !fal
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[2]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[3]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[4]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: data, !NoneTrue() for: { true, true, true, true, true } not contains no true
MatchersRanges.tests.cpp:<line number>: passed: data, NoneTrue() for: { } contains no true
MatchersRanges.tests.cpp:<line number>: passed: data, !NoneTrue() for: { false, false, true, false, false } not contains no true
MatchersRanges.tests.cpp:<line number>: passed: data, NoneTrue() for: { false, false, false, false, false } contains no true
MatchersRanges.tests.cpp:<line number>: passed: data, !NoneTrue() for: { true, true, true, true, true } not contains no true
MatchersRanges.tests.cpp:<line number>: passed: data, !NoneTrue() for: { false, false, true, false, false } not contains no true
MatchersRanges.tests.cpp:<line number>: passed: data, NoneTrue() for: { false, false, false, false, false } contains no true
MatchersRanges.tests.cpp:<line number>: passed: mocked, NoneTrue() for: { false, false, false, false, false } contains no true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[0] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[3] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[4] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked, !NoneTrue() for: { false, false, true, true, true } not contains no true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[0] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[1] for: true
MatchersRanges.tests.cpp:<line number>: passed: mocked.m_derefed[2] for: true
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[3]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: !(mocked.m_derefed[4]) for: !false
MatchersRanges.tests.cpp:<line number>: passed: empty_vec, SizeIs(0) for: { } has size == 0
MatchersRanges.tests.cpp:<line number>: passed: empty_vec, !SizeIs(2) for: { } not has size == 2
MatchersRanges.tests.cpp:<line number>: passed: empty_vec, SizeIs(Lt(2)) for: { } size matches is less than 2
Expand Down