mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-11-04 05:59:32 +01:00 
			
		
		
		
	Add more comprehensive tests for the quantifier matchers
This includes * Testing both positive and negative path through the matchers * Testing them with types whose `begin` and `end` member functions require ADL * Testing them with types that return different types from `begin` and `end`
This commit is contained in:
		@@ -6,6 +6,7 @@
 | 
			
		||||
#include <catch2/matchers/catch_matchers_contains.hpp>
 | 
			
		||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
 | 
			
		||||
#include <catch2/matchers/catch_matchers_quantifiers.hpp>
 | 
			
		||||
#include <catch2/matchers/catch_matchers_predicate.hpp>
 | 
			
		||||
#include <catch2/matchers/catch_matchers_string.hpp>
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
@@ -33,8 +34,143 @@ namespace unrelated {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
} // end unrelated namespace
 | 
			
		||||
 | 
			
		||||
#if defined(__clang__)
 | 
			
		||||
#  pragma clang diagnostic push
 | 
			
		||||
#  pragma clang diagnostic ignored "-Wunused-function"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
class has_different_begin_end_types {
 | 
			
		||||
    std::array<int, 5> elements{ {1, 2, 3, 4, 5} };
 | 
			
		||||
 | 
			
		||||
    // Different type for the "end" iterator
 | 
			
		||||
    struct iterator_end {};
 | 
			
		||||
    // Just a fake forward iterator, that only compares to a different
 | 
			
		||||
    // type (so we can test two-type ranges).
 | 
			
		||||
    struct iterator {
 | 
			
		||||
        int const* start;
 | 
			
		||||
        int const* end;
 | 
			
		||||
 | 
			
		||||
        using iterator_category = std::forward_iterator_tag;
 | 
			
		||||
        using difference_type = std::ptrdiff_t;
 | 
			
		||||
        using value_type = int;
 | 
			
		||||
        using reference = int const&;
 | 
			
		||||
        using pointer = int const*;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        friend bool operator==( iterator iter, iterator_end ) {
 | 
			
		||||
            return iter.start == iter.end;
 | 
			
		||||
        }
 | 
			
		||||
        friend bool operator!=( iterator iter, iterator_end ) {
 | 
			
		||||
            return iter.start != iter.end;
 | 
			
		||||
        }
 | 
			
		||||
        iterator& operator++() {
 | 
			
		||||
            ++start;
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        iterator operator++(int) {
 | 
			
		||||
            auto tmp(*this);
 | 
			
		||||
            ++start;
 | 
			
		||||
            return tmp;
 | 
			
		||||
        }
 | 
			
		||||
        reference operator*() const {
 | 
			
		||||
            return *start;
 | 
			
		||||
        }
 | 
			
		||||
        pointer operator->() const {
 | 
			
		||||
            return start;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    iterator begin() const {
 | 
			
		||||
        return { elements.data(), elements.data() + elements.size() };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    iterator_end end() const {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#if defined(__clang__)
 | 
			
		||||
#  pragma clang diagnostic pop
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct with_mocked_iterator_access {
 | 
			
		||||
    static constexpr size_t data_size = 5;
 | 
			
		||||
    std::array<int, data_size> elements{ {1, 2, 3, 4, 5} };
 | 
			
		||||
    std::array<bool, data_size> touched{};
 | 
			
		||||
    std::array<bool, data_size> derefed{};
 | 
			
		||||
 | 
			
		||||
    // We want to check which elements were touched when iterating, so
 | 
			
		||||
    // we can check whether iterator-using code traverses range correctly
 | 
			
		||||
    struct iterator {
 | 
			
		||||
        with_mocked_iterator_access* m_origin;
 | 
			
		||||
        size_t m_origin_idx;
 | 
			
		||||
 | 
			
		||||
        using iterator_category = std::forward_iterator_tag;
 | 
			
		||||
        using difference_type = std::ptrdiff_t;
 | 
			
		||||
        using value_type = int;
 | 
			
		||||
        using reference = int const&;
 | 
			
		||||
        using pointer = int const*;
 | 
			
		||||
 | 
			
		||||
        friend bool operator==(iterator lhs, iterator rhs) {
 | 
			
		||||
            return lhs.m_origin == rhs.m_origin
 | 
			
		||||
                && lhs.m_origin_idx == rhs.m_origin_idx;
 | 
			
		||||
        }
 | 
			
		||||
        friend bool operator!=(iterator lhs, iterator rhs) {
 | 
			
		||||
            return !(lhs == rhs);
 | 
			
		||||
        }
 | 
			
		||||
        iterator& operator++() {
 | 
			
		||||
            ++m_origin_idx;
 | 
			
		||||
            assert(m_origin_idx < data_size + 1 && "Outside of valid alloc");
 | 
			
		||||
            if (m_origin_idx < data_size) {
 | 
			
		||||
                m_origin->touched[m_origin_idx] = true;
 | 
			
		||||
            }
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        iterator operator++(int) {
 | 
			
		||||
            auto tmp(*this);
 | 
			
		||||
            ++(*this);
 | 
			
		||||
            return tmp;
 | 
			
		||||
        }
 | 
			
		||||
        reference operator*() const {
 | 
			
		||||
            assert(m_origin_idx < data_size && "Attempted to deref invalid position");
 | 
			
		||||
            m_origin->derefed[m_origin_idx] = true;
 | 
			
		||||
            return m_origin->elements[m_origin_idx];
 | 
			
		||||
        }
 | 
			
		||||
        pointer operator->() const {
 | 
			
		||||
            assert(m_origin_idx < data_size && "Attempted to deref invalid position");
 | 
			
		||||
            return &m_origin->elements[m_origin_idx];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    iterator begin() const {
 | 
			
		||||
        // Const-cast away to avoid overcomplicating the iterators
 | 
			
		||||
        // We should actually fix this over time
 | 
			
		||||
        return { const_cast<with_mocked_iterator_access*>(this), 0 };
 | 
			
		||||
    }
 | 
			
		||||
    iterator end() const {
 | 
			
		||||
        return { const_cast<with_mocked_iterator_access*>(this), data_size };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // end anon namespace
 | 
			
		||||
 | 
			
		||||
namespace Catch {
 | 
			
		||||
    template <>
 | 
			
		||||
    struct StringMaker<with_mocked_iterator_access> {
 | 
			
		||||
        static std::string convert(with_mocked_iterator_access const& access) {
 | 
			
		||||
            // We have to avoid the type's iterators, because we check
 | 
			
		||||
            // their use in tests
 | 
			
		||||
            return ::Catch::Detail::stringify(access.elements);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct MoveOnlyTestElement {
 | 
			
		||||
    int num = 0;
 | 
			
		||||
    MoveOnlyTestElement(int n) :num(n) {}
 | 
			
		||||
@@ -220,55 +356,190 @@ TEST_CASE("Usage of the SizeIs range matcher", "[matchers][templated][size]") {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Usage of the quantifiers matchers", "[matchers][templated][quantifiers]") {
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Usage of AllMatch range matcher", "[matchers][templated][quantifiers]") {
 | 
			
		||||
    using Catch::Matchers::AllMatch;
 | 
			
		||||
    using Catch::Matchers::AnyMatch;
 | 
			
		||||
    using Catch::Matchers::Contains;
 | 
			
		||||
    using Catch::Matchers::EndsWith;
 | 
			
		||||
    using Catch::Matchers::IsEmpty;
 | 
			
		||||
    using Catch::Matchers::NoneMatch;
 | 
			
		||||
    using Catch::Matchers::SizeIs;
 | 
			
		||||
    using Catch::Matchers::StartsWith;
 | 
			
		||||
    std::array<std::array<int, 5>, 5> int_arr_arr{{
 | 
			
		||||
                                            {{ 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 }}
 | 
			
		||||
    }};
 | 
			
		||||
    std::vector<std::string> string_vector{ "Command+", "Catch2+", "CMake+", "C++", "Console+" };
 | 
			
		||||
    std::list<std::vector<int>> int_vectors_list{ { 1, 2 }, { 3, 4 }, { 5, 6 } };
 | 
			
		||||
    using Catch::Matchers::Predicate;
 | 
			
		||||
 | 
			
		||||
    SECTION("Usage of the AllMatch range matcher") {
 | 
			
		||||
        REQUIRE_THAT(int_arr_arr, AllMatch(Contains(0)));
 | 
			
		||||
        REQUIRE_THAT(int_arr_arr, AllMatch(SizeIs(5)));
 | 
			
		||||
    SECTION("Basic usage") {
 | 
			
		||||
        using Catch::Matchers::Contains;
 | 
			
		||||
        using Catch::Matchers::SizeIs;
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(string_vector, AllMatch(StartsWith("C")));
 | 
			
		||||
        REQUIRE_THAT(string_vector, AllMatch(EndsWith("+")));
 | 
			
		||||
        std::array<std::array<int, 5>, 5> data{{
 | 
			
		||||
                                        {{ 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 }}
 | 
			
		||||
        }};
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(int_vectors_list, AllMatch(!IsEmpty()));
 | 
			
		||||
        REQUIRE_THAT(int_vectors_list, AllMatch(SizeIs(2)));
 | 
			
		||||
        REQUIRE_THAT(data,  AllMatch(SizeIs(5)));
 | 
			
		||||
        REQUIRE_THAT(data, !AllMatch(Contains(0) && Contains(1)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("Usage of the AnyMatch range matcher") {
 | 
			
		||||
        REQUIRE_THAT(int_arr_arr, AnyMatch(Contains(-2)));
 | 
			
		||||
        REQUIRE_THAT(int_arr_arr, AnyMatch(SizeIs(5)));
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(string_vector, AnyMatch(StartsWith("CMak")));
 | 
			
		||||
        REQUIRE_THAT(string_vector, AnyMatch(EndsWith("++")));
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(int_vectors_list, AnyMatch(Contains(4)));
 | 
			
		||||
        REQUIRE_THAT(int_vectors_list, AnyMatch(SizeIs(2)));
 | 
			
		||||
    SECTION("Type requires ADL found begin and end") {
 | 
			
		||||
        unrelated::needs_ADL_begin needs_adl;
 | 
			
		||||
        REQUIRE_THAT( needs_adl, AllMatch( Predicate<int>( []( int elem ) {
 | 
			
		||||
                          return elem < 6;
 | 
			
		||||
                      } ) ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("Usage of the NoneMatch range matcher") {
 | 
			
		||||
        REQUIRE_THAT(int_arr_arr, NoneMatch(Contains(-6)));
 | 
			
		||||
        REQUIRE_THAT(int_arr_arr, NoneMatch(SizeIs(42)));
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(string_vector, NoneMatch(StartsWith("abd")));
 | 
			
		||||
        REQUIRE_THAT(string_vector, NoneMatch(EndsWith("#!--")));
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(int_vectors_list, NoneMatch(IsEmpty()));
 | 
			
		||||
        REQUIRE_THAT(int_vectors_list, NoneMatch(SizeIs(3)));
 | 
			
		||||
    SECTION("Shortcircuiting") {
 | 
			
		||||
        with_mocked_iterator_access mocked;
 | 
			
		||||
        SECTION("All are read") {
 | 
			
		||||
            auto allMatch = AllMatch(Predicate<int>([](int elem) {
 | 
			
		||||
                return elem < 10;
 | 
			
		||||
            }));
 | 
			
		||||
            REQUIRE_THAT(mocked, allMatch);
 | 
			
		||||
            REQUIRE(mocked.derefed[0]);
 | 
			
		||||
            REQUIRE(mocked.derefed[1]);
 | 
			
		||||
            REQUIRE(mocked.derefed[2]);
 | 
			
		||||
            REQUIRE(mocked.derefed[3]);
 | 
			
		||||
            REQUIRE(mocked.derefed[4]);
 | 
			
		||||
        }
 | 
			
		||||
        SECTION("Short-circuited") {
 | 
			
		||||
            auto allMatch = AllMatch(Predicate<int>([](int elem) {
 | 
			
		||||
                return elem < 3;
 | 
			
		||||
            }));
 | 
			
		||||
            REQUIRE_THAT(mocked, !allMatch);
 | 
			
		||||
            REQUIRE(mocked.derefed[0]);
 | 
			
		||||
            REQUIRE(mocked.derefed[1]);
 | 
			
		||||
            REQUIRE(mocked.derefed[2]);
 | 
			
		||||
            REQUIRE_FALSE(mocked.derefed[3]);
 | 
			
		||||
            REQUIRE_FALSE(mocked.derefed[4]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Usage of AnyMatch range matcher", "[matchers][templated][quantifiers]") {
 | 
			
		||||
    using Catch::Matchers::AnyMatch;
 | 
			
		||||
    using Catch::Matchers::Predicate;
 | 
			
		||||
 | 
			
		||||
    SECTION("Basic usage") {
 | 
			
		||||
        using Catch::Matchers::Contains;
 | 
			
		||||
        using Catch::Matchers::SizeIs;
 | 
			
		||||
 | 
			
		||||
        std::array<std::array<int, 5>, 5> data{ {
 | 
			
		||||
                                        {{ 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 }}
 | 
			
		||||
        } };
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(data,  AnyMatch(SizeIs(5)));
 | 
			
		||||
        REQUIRE_THAT(data, !AnyMatch(Contains(0) && Contains(10)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("Type requires ADL found begin and end") {
 | 
			
		||||
        unrelated::needs_ADL_begin needs_adl;
 | 
			
		||||
        REQUIRE_THAT( needs_adl, AnyMatch( Predicate<int>( []( int elem ) {
 | 
			
		||||
                          return elem < 3;
 | 
			
		||||
                      } ) ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("Shortcircuiting") {
 | 
			
		||||
        with_mocked_iterator_access mocked;
 | 
			
		||||
        SECTION("All are read") {
 | 
			
		||||
            auto anyMatch = AnyMatch(
 | 
			
		||||
                Predicate<int>( []( int elem ) { return elem > 10; } ) );
 | 
			
		||||
            REQUIRE_THAT( mocked, !anyMatch );
 | 
			
		||||
            REQUIRE( mocked.derefed[0] );
 | 
			
		||||
            REQUIRE( mocked.derefed[1] );
 | 
			
		||||
            REQUIRE( mocked.derefed[2] );
 | 
			
		||||
            REQUIRE( mocked.derefed[3] );
 | 
			
		||||
            REQUIRE( mocked.derefed[4] );
 | 
			
		||||
        }
 | 
			
		||||
        SECTION("Short-circuited") {
 | 
			
		||||
            auto anyMatch = AnyMatch(
 | 
			
		||||
                Predicate<int>( []( int elem ) { return elem < 3; } ) );
 | 
			
		||||
            REQUIRE_THAT( mocked, anyMatch );
 | 
			
		||||
            REQUIRE( mocked.derefed[0] );
 | 
			
		||||
            REQUIRE_FALSE( mocked.derefed[1] );
 | 
			
		||||
            REQUIRE_FALSE( mocked.derefed[2] );
 | 
			
		||||
            REQUIRE_FALSE( mocked.derefed[3] );
 | 
			
		||||
            REQUIRE_FALSE( mocked.derefed[4] );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TEST_CASE("Usage of NoneMatch range matcher", "[matchers][templated][quantifiers]") {
 | 
			
		||||
    using Catch::Matchers::NoneMatch;
 | 
			
		||||
    using Catch::Matchers::Predicate;
 | 
			
		||||
 | 
			
		||||
    SECTION("Basic usage") {
 | 
			
		||||
        using Catch::Matchers::Contains;
 | 
			
		||||
        using Catch::Matchers::SizeIs;
 | 
			
		||||
 | 
			
		||||
        std::array<std::array<int, 5>, 5> data{ {
 | 
			
		||||
                                        {{ 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 }}
 | 
			
		||||
        } };
 | 
			
		||||
 | 
			
		||||
        REQUIRE_THAT(data, NoneMatch(SizeIs(6)));
 | 
			
		||||
        REQUIRE_THAT(data, !NoneMatch(Contains(0) && Contains(1)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION( "Type requires ADL found begin and end" ) {
 | 
			
		||||
        unrelated::needs_ADL_begin needs_adl;
 | 
			
		||||
        REQUIRE_THAT( needs_adl, NoneMatch( Predicate<int>( []( int elem ) {
 | 
			
		||||
                          return elem > 6;
 | 
			
		||||
                      } ) ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    SECTION("Shortcircuiting") {
 | 
			
		||||
        with_mocked_iterator_access mocked;
 | 
			
		||||
        SECTION("All are read") {
 | 
			
		||||
            auto noneMatch = NoneMatch(
 | 
			
		||||
                Predicate<int>([](int elem) { return elem > 10; }));
 | 
			
		||||
            REQUIRE_THAT(mocked, noneMatch);
 | 
			
		||||
            REQUIRE(mocked.derefed[0]);
 | 
			
		||||
            REQUIRE(mocked.derefed[1]);
 | 
			
		||||
            REQUIRE(mocked.derefed[2]);
 | 
			
		||||
            REQUIRE(mocked.derefed[3]);
 | 
			
		||||
            REQUIRE(mocked.derefed[4]);
 | 
			
		||||
        }
 | 
			
		||||
        SECTION("Short-circuited") {
 | 
			
		||||
            auto noneMatch = NoneMatch(
 | 
			
		||||
                Predicate<int>([](int elem) { return elem < 3; }));
 | 
			
		||||
            REQUIRE_THAT(mocked, !noneMatch);
 | 
			
		||||
            REQUIRE(mocked.derefed[0]);
 | 
			
		||||
            REQUIRE_FALSE(mocked.derefed[1]);
 | 
			
		||||
            REQUIRE_FALSE(mocked.derefed[2]);
 | 
			
		||||
            REQUIRE_FALSE(mocked.derefed[3]);
 | 
			
		||||
            REQUIRE_FALSE(mocked.derefed[4]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// This is a C++17 extension, and GCC refuses to compile such code
 | 
			
		||||
// unless it is set to C++17 or later
 | 
			
		||||
#if defined(CATCH_CPP17_OR_GREATER)
 | 
			
		||||
 | 
			
		||||
TEST_CASE( "The quantifier range matchers support types with different types returned from begin and end",
 | 
			
		||||
           "[matchers][templated][quantifiers][approvals]" ) {
 | 
			
		||||
    using Catch::Matchers::AllMatch;
 | 
			
		||||
    using Catch::Matchers::AnyMatch;
 | 
			
		||||
    using Catch::Matchers::NoneMatch;
 | 
			
		||||
 | 
			
		||||
    using Catch::Matchers::Predicate;
 | 
			
		||||
 | 
			
		||||
    has_different_begin_end_types diff_types;
 | 
			
		||||
    REQUIRE_THAT( diff_types, !AllMatch( Predicate<int>( []( int elem ) {
 | 
			
		||||
                      return elem < 3;
 | 
			
		||||
                  } ) ) );
 | 
			
		||||
 | 
			
		||||
    REQUIRE_THAT( diff_types, AnyMatch( Predicate<int>( []( int elem ) {
 | 
			
		||||
                      return elem < 2;
 | 
			
		||||
                  } ) ) );
 | 
			
		||||
 | 
			
		||||
    REQUIRE_THAT( diff_types, !NoneMatch( Predicate<int>( []( int elem ) {
 | 
			
		||||
                      return elem < 3;
 | 
			
		||||
                  } ) ) );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user