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:
Martin Hořeňovský
2020-12-26 23:09:51 +01:00
parent 552af8920d
commit 77643ce2e5
10 changed files with 1238 additions and 394 deletions

View File

@@ -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