mirror of
https://github.com/catchorg/Catch2.git
synced 2024-11-25 23:06:10 +01:00
Add Catch::Detail::is_permutation that supports sentinels
Also split out helpers for testing matcher ranges (types whose begin/end/empty/etc require ADL lookup, types whose iteration uses iterator + sentinel pair, etc) into their own file.
This commit is contained in:
parent
f3c0a3cd09
commit
772fa3f790
@ -88,6 +88,7 @@ set(IMPL_HEADERS
|
|||||||
${SOURCES_DIR}/internal/catch_floating_point_helpers.hpp
|
${SOURCES_DIR}/internal/catch_floating_point_helpers.hpp
|
||||||
${SOURCES_DIR}/internal/catch_getenv.hpp
|
${SOURCES_DIR}/internal/catch_getenv.hpp
|
||||||
${SOURCES_DIR}/internal/catch_istream.hpp
|
${SOURCES_DIR}/internal/catch_istream.hpp
|
||||||
|
${SOURCES_DIR}/internal/catch_is_permutation.hpp
|
||||||
${SOURCES_DIR}/internal/catch_lazy_expr.hpp
|
${SOURCES_DIR}/internal/catch_lazy_expr.hpp
|
||||||
${SOURCES_DIR}/internal/catch_leak_detector.hpp
|
${SOURCES_DIR}/internal/catch_leak_detector.hpp
|
||||||
${SOURCES_DIR}/internal/catch_list.hpp
|
${SOURCES_DIR}/internal/catch_list.hpp
|
||||||
|
@ -70,6 +70,7 @@
|
|||||||
#include <catch2/internal/catch_fatal_condition_handler.hpp>
|
#include <catch2/internal/catch_fatal_condition_handler.hpp>
|
||||||
#include <catch2/internal/catch_floating_point_helpers.hpp>
|
#include <catch2/internal/catch_floating_point_helpers.hpp>
|
||||||
#include <catch2/internal/catch_getenv.hpp>
|
#include <catch2/internal/catch_getenv.hpp>
|
||||||
|
#include <catch2/internal/catch_is_permutation.hpp>
|
||||||
#include <catch2/internal/catch_istream.hpp>
|
#include <catch2/internal/catch_istream.hpp>
|
||||||
#include <catch2/internal/catch_lazy_expr.hpp>
|
#include <catch2/internal/catch_lazy_expr.hpp>
|
||||||
#include <catch2/internal/catch_leak_detector.hpp>
|
#include <catch2/internal/catch_leak_detector.hpp>
|
||||||
|
138
src/catch2/internal/catch_is_permutation.hpp
Normal file
138
src/catch2/internal/catch_is_permutation.hpp
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
|
||||||
|
// Copyright Catch2 Authors
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// (See accompanying file LICENSE.txt or copy at
|
||||||
|
// https://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
|
#ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED
|
||||||
|
#define CATCH_IS_PERMUTATION_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
|
namespace Catch {
|
||||||
|
namespace Detail {
|
||||||
|
|
||||||
|
template <typename ForwardIter,
|
||||||
|
typename Sentinel,
|
||||||
|
typename T,
|
||||||
|
typename Comparator>
|
||||||
|
ForwardIter find_sentinel( ForwardIter start,
|
||||||
|
Sentinel sentinel,
|
||||||
|
T const& value,
|
||||||
|
Comparator cmp ) {
|
||||||
|
while ( start != sentinel ) {
|
||||||
|
if ( cmp( *start, value ) ) { break; }
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ForwardIter,
|
||||||
|
typename Sentinel,
|
||||||
|
typename T,
|
||||||
|
typename Comparator>
|
||||||
|
std::ptrdiff_t count_sentinel( ForwardIter start,
|
||||||
|
Sentinel sentinel,
|
||||||
|
T const& value,
|
||||||
|
Comparator cmp ) {
|
||||||
|
std::ptrdiff_t count = 0;
|
||||||
|
while ( start != sentinel ) {
|
||||||
|
if ( cmp( *start, value ) ) { ++count; }
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ForwardIter, typename Sentinel>
|
||||||
|
std::enable_if_t<!std::is_same<ForwardIter, Sentinel>::value,
|
||||||
|
std::ptrdiff_t>
|
||||||
|
sentinel_distance( ForwardIter iter, const Sentinel sentinel ) {
|
||||||
|
std::ptrdiff_t dist = 0;
|
||||||
|
while ( iter != sentinel ) {
|
||||||
|
++iter;
|
||||||
|
++dist;
|
||||||
|
}
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ForwardIter>
|
||||||
|
std::ptrdiff_t sentinel_distance( ForwardIter first,
|
||||||
|
ForwardIter last ) {
|
||||||
|
return std::distance( first, last );
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ForwardIter1,
|
||||||
|
typename Sentinel1,
|
||||||
|
typename ForwardIter2,
|
||||||
|
typename Sentinel2,
|
||||||
|
typename Comparator>
|
||||||
|
bool check_element_counts( ForwardIter1 first_1,
|
||||||
|
const Sentinel1 end_1,
|
||||||
|
ForwardIter2 first_2,
|
||||||
|
const Sentinel2 end_2,
|
||||||
|
Comparator cmp ) {
|
||||||
|
auto cursor = first_1;
|
||||||
|
while ( cursor != end_1 ) {
|
||||||
|
if ( find_sentinel( first_1, cursor, *cursor, cmp ) ==
|
||||||
|
cursor ) {
|
||||||
|
// we haven't checked this element yet
|
||||||
|
const auto count_in_range_2 =
|
||||||
|
count_sentinel( first_2, end_2, *cursor, cmp );
|
||||||
|
// Not a single instance in 2nd range, so it cannot be a
|
||||||
|
// permutation of 1st range
|
||||||
|
if ( count_in_range_2 == 0 ) { return false; }
|
||||||
|
|
||||||
|
const auto count_in_range_1 =
|
||||||
|
count_sentinel( cursor, end_1, *cursor, cmp );
|
||||||
|
if ( count_in_range_1 != count_in_range_2 ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ForwardIter1,
|
||||||
|
typename Sentinel1,
|
||||||
|
typename ForwardIter2,
|
||||||
|
typename Sentinel2,
|
||||||
|
typename Comparator>
|
||||||
|
bool is_permutation( ForwardIter1 first_1,
|
||||||
|
const Sentinel1 end_1,
|
||||||
|
ForwardIter2 first_2,
|
||||||
|
const Sentinel2 end_2,
|
||||||
|
Comparator cmp ) {
|
||||||
|
// TODO: no optimization for stronger iterators, because we would also have to constrain on sentinel vs not sentinel types
|
||||||
|
// TODO: Comparator has to be "both sides", e.g. a == b => b == a
|
||||||
|
// This skips shared prefix of the two ranges
|
||||||
|
while (first_1 != end_1 && first_2 != end_2 && cmp(*first_1, *first_2)) {
|
||||||
|
++first_1;
|
||||||
|
++first_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to handle case where at least one of the ranges has no more elements
|
||||||
|
if (first_1 == end_1 || first_2 == end_2) {
|
||||||
|
return first_1 == end_1 && first_2 == end_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pair counting is n**2, so we pay linear walk to compare the sizes first
|
||||||
|
auto dist_1 = sentinel_distance( first_1, end_1 );
|
||||||
|
auto dist_2 = sentinel_distance( first_2, end_2 );
|
||||||
|
|
||||||
|
if (dist_1 != dist_2) { return false; }
|
||||||
|
|
||||||
|
// Since we do not try to handle stronger iterators pair (e.g.
|
||||||
|
// bidir) optimally, the only thing left to do is to check counts in
|
||||||
|
// the remaining ranges.
|
||||||
|
return check_element_counts( first_1, end_1, first_2, end_2, cmp );
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
} // namespace Catch
|
||||||
|
|
||||||
|
#endif // CATCH_IS_PERMUTATION_HPP_INCLUDED
|
@ -93,6 +93,7 @@ internal_headers = [
|
|||||||
'internal/catch_floating_point_helpers.hpp',
|
'internal/catch_floating_point_helpers.hpp',
|
||||||
'internal/catch_getenv.hpp',
|
'internal/catch_getenv.hpp',
|
||||||
'internal/catch_istream.hpp',
|
'internal/catch_istream.hpp',
|
||||||
|
'internal/catch_is_permutation.hpp',
|
||||||
'internal/catch_lazy_expr.hpp',
|
'internal/catch_lazy_expr.hpp',
|
||||||
'internal/catch_leak_detector.hpp',
|
'internal/catch_leak_detector.hpp',
|
||||||
'internal/catch_list.hpp',
|
'internal/catch_list.hpp',
|
||||||
|
@ -77,6 +77,7 @@ endif(MSVC) #Temporary workaround
|
|||||||
# Please keep these ordered alphabetically
|
# Please keep these ordered alphabetically
|
||||||
set(TEST_SOURCES
|
set(TEST_SOURCES
|
||||||
${SELF_TEST_DIR}/TestRegistrations.cpp
|
${SELF_TEST_DIR}/TestRegistrations.cpp
|
||||||
|
${SELF_TEST_DIR}/IntrospectiveTests/Algorithms.tests.cpp
|
||||||
${SELF_TEST_DIR}/IntrospectiveTests/Clara.tests.cpp
|
${SELF_TEST_DIR}/IntrospectiveTests/Clara.tests.cpp
|
||||||
${SELF_TEST_DIR}/IntrospectiveTests/CmdLine.tests.cpp
|
${SELF_TEST_DIR}/IntrospectiveTests/CmdLine.tests.cpp
|
||||||
${SELF_TEST_DIR}/IntrospectiveTests/CmdLineHelpers.tests.cpp
|
${SELF_TEST_DIR}/IntrospectiveTests/CmdLineHelpers.tests.cpp
|
||||||
@ -134,6 +135,7 @@ set(TEST_SOURCES
|
|||||||
|
|
||||||
set(TEST_HEADERS
|
set(TEST_HEADERS
|
||||||
${SELF_TEST_DIR}/helpers/parse_test_spec.hpp
|
${SELF_TEST_DIR}/helpers/parse_test_spec.hpp
|
||||||
|
${SELF_TEST_DIR}/helpers/range_test_helpers.hpp
|
||||||
${SELF_TEST_DIR}/helpers/type_with_lit_0_comparisons.hpp
|
${SELF_TEST_DIR}/helpers/type_with_lit_0_comparisons.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
94
tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
Normal file
94
tests/SelfTest/IntrospectiveTests/Algorithms.tests.cpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
|
||||||
|
// Copyright Catch2 Authors
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// (See accompanying file LICENSE.txt or copy at
|
||||||
|
// https://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <catch2/internal/catch_is_permutation.hpp>
|
||||||
|
|
||||||
|
#include <helpers/range_test_helpers.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template <typename Range1, typename Range2>
|
||||||
|
static bool is_permutation(Range1 const& r1, Range2 const& r2) {
|
||||||
|
using std::begin; using std::end;
|
||||||
|
return Catch::Detail::is_permutation(
|
||||||
|
begin( r1 ), end( r1 ), begin( r2 ), end( r2 ), std::equal_to<>{} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_permutation", "[algorithms][approvals]") {
|
||||||
|
SECTION( "Handle empty ranges" ) {
|
||||||
|
std::array<int, 0> empty;
|
||||||
|
std::array<int, 2> non_empty{ { 2, 3 } };
|
||||||
|
REQUIRE( is_permutation( empty, empty ) );
|
||||||
|
REQUIRE_FALSE( is_permutation( empty, non_empty ) );
|
||||||
|
REQUIRE_FALSE( is_permutation( non_empty, empty ) );
|
||||||
|
}
|
||||||
|
SECTION( "Different length ranges" ) {
|
||||||
|
std::array<int, 6> arr1{ { 1, 3, 5, 7, 8, 9 } };
|
||||||
|
// arr2 is prefix of arr1
|
||||||
|
std::array<int, 4> arr2{ { 1, 3, 5, 7 } };
|
||||||
|
// arr3 shares prefix with arr1 and arr2, but is not a permutation
|
||||||
|
std::array<int, 5> arr3{ { 1, 3, 5, 9, 8 } };
|
||||||
|
REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
|
||||||
|
REQUIRE_FALSE( is_permutation( arr1, arr3 ) );
|
||||||
|
REQUIRE_FALSE( is_permutation( arr2, arr3 ) );
|
||||||
|
}
|
||||||
|
SECTION( "Same length ranges" ) {
|
||||||
|
SECTION( "Shared elements, but different counts" ) {
|
||||||
|
const std::array<int, 6>
|
||||||
|
arr1{ { 1, 1, 1, 1, 2, 2 } },
|
||||||
|
arr2{ { 1, 1, 2, 2, 2, 2 } };
|
||||||
|
REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
|
||||||
|
}
|
||||||
|
SECTION( "Identical ranges" ) {
|
||||||
|
const std::array<int, 6>
|
||||||
|
arr1{ { 1, 1, 1, 1, 2, 2 } },
|
||||||
|
arr2{ { 1, 1, 2, 2, 2, 2 } };
|
||||||
|
REQUIRE( is_permutation( arr1, arr1 ) );
|
||||||
|
REQUIRE( is_permutation( arr2, arr2 ) );
|
||||||
|
}
|
||||||
|
SECTION( "Completely distinct elements" ) {
|
||||||
|
// Completely distinct elements
|
||||||
|
const std::array<int, 4>
|
||||||
|
arr1{ { 1, 2, 3, 4 } },
|
||||||
|
arr2{ { 10, 20, 30, 40 } };
|
||||||
|
REQUIRE_FALSE( is_permutation( arr1, arr2 ) );
|
||||||
|
}
|
||||||
|
SECTION( "Reverse ranges" ) {
|
||||||
|
const std::array<int, 5>
|
||||||
|
arr1{ { 1, 2, 3, 4, 5 } },
|
||||||
|
arr2{ { 5, 4, 3, 2, 1 } };
|
||||||
|
REQUIRE( is_permutation( arr1, arr2 ) );
|
||||||
|
}
|
||||||
|
SECTION( "Shared prefix & permuted elements" ) {
|
||||||
|
const std::array<int, 5>
|
||||||
|
arr1{ { 1, 1, 2, 3, 4 } },
|
||||||
|
arr2{ { 1, 1, 4, 2, 3 } };
|
||||||
|
REQUIRE( is_permutation( arr1, arr2 ) );
|
||||||
|
}
|
||||||
|
SECTION( "Permutations with element count > 1" ) {
|
||||||
|
const std::array<int, 7>
|
||||||
|
arr1{ { 2, 2, 3, 3, 3, 1, 1 } },
|
||||||
|
arr2{ { 3, 2, 1, 3, 2, 1, 3 } };
|
||||||
|
REQUIRE( is_permutation( arr1, arr2 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("is_permutation supports iterator + sentinel pairs",
|
||||||
|
"[algorithms][is-permutation][approvals]") {
|
||||||
|
const has_different_begin_end_types<int>
|
||||||
|
range_1{ 1, 2, 3, 4 },
|
||||||
|
range_2{ 4, 3, 2, 1 };
|
||||||
|
REQUIRE( is_permutation( range_1, range_2 ) );
|
||||||
|
|
||||||
|
const has_different_begin_end_types<int> range_3{ 3, 3, 2, 1 };
|
||||||
|
REQUIRE_FALSE( is_permutation( range_1, range_3 ) );
|
||||||
|
}
|
@ -15,195 +15,15 @@
|
|||||||
#include <catch2/matchers/catch_matchers_predicate.hpp>
|
#include <catch2/matchers/catch_matchers_predicate.hpp>
|
||||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||||
|
|
||||||
|
#include <helpers/range_test_helpers.hpp>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <initializer_list>
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
namespace unrelated {
|
|
||||||
template <typename T>
|
|
||||||
class needs_ADL_begin {
|
|
||||||
std::vector<T> m_elements;
|
|
||||||
public:
|
|
||||||
using iterator = typename std::vector<T>::iterator;
|
|
||||||
using const_iterator = typename std::vector<T>::const_iterator;
|
|
||||||
|
|
||||||
needs_ADL_begin(std::initializer_list<T> init) : m_elements(init) {}
|
|
||||||
|
|
||||||
const_iterator Begin() const { return m_elements.begin(); }
|
|
||||||
const_iterator End() const { return m_elements.end(); }
|
|
||||||
|
|
||||||
friend const_iterator begin(needs_ADL_begin const& lhs) {
|
|
||||||
return lhs.Begin();
|
|
||||||
}
|
|
||||||
friend const_iterator end(needs_ADL_begin const& rhs) {
|
|
||||||
return rhs.End();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // end unrelated namespace
|
|
||||||
|
|
||||||
#if defined(__clang__)
|
|
||||||
# pragma clang diagnostic push
|
|
||||||
# pragma clang diagnostic ignored "-Wunused-function"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class has_different_begin_end_types {
|
|
||||||
// Using std::vector<T> leads to annoying issues when T is bool
|
|
||||||
// so we just use list because the perf is not critical and ugh.
|
|
||||||
std::list<T> m_elements;
|
|
||||||
|
|
||||||
// Different type for the "end" iterator
|
|
||||||
struct iterator_end {};
|
|
||||||
// Fake-ish forward iterator that only compares to a different type
|
|
||||||
class iterator {
|
|
||||||
using underlying_iter = typename std::list<T>::const_iterator;
|
|
||||||
underlying_iter m_start;
|
|
||||||
underlying_iter m_end;
|
|
||||||
|
|
||||||
public:
|
|
||||||
iterator( underlying_iter start, underlying_iter end ):
|
|
||||||
m_start( start ), m_end( end ) {}
|
|
||||||
|
|
||||||
using iterator_category = std::forward_iterator_tag;
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using value_type = T;
|
|
||||||
using const_reference = T const&;
|
|
||||||
using pointer = T const*;
|
|
||||||
|
|
||||||
|
|
||||||
friend bool operator==( iterator iter, iterator_end ) {
|
|
||||||
return iter.m_start == iter.m_end;
|
|
||||||
}
|
|
||||||
friend bool operator!=( iterator iter, iterator_end ) {
|
|
||||||
return iter.m_start != iter.m_end;
|
|
||||||
}
|
|
||||||
iterator& operator++() {
|
|
||||||
++m_start;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
iterator operator++(int) {
|
|
||||||
auto tmp(*this);
|
|
||||||
++m_start;
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
const_reference operator*() const {
|
|
||||||
return *m_start;
|
|
||||||
}
|
|
||||||
pointer operator->() const {
|
|
||||||
return m_start;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit has_different_begin_end_types( std::initializer_list<T> init ):
|
|
||||||
m_elements( init ) {}
|
|
||||||
|
|
||||||
iterator begin() const {
|
|
||||||
return { m_elements.begin(), m_elements.end() };
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator_end end() const {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__clang__)
|
|
||||||
# pragma clang diagnostic pop
|
|
||||||
#endif
|
|
||||||
|
|
||||||
template <typename T> struct with_mocked_iterator_access {
|
|
||||||
std::vector<T> m_elements;
|
|
||||||
|
|
||||||
// use plain arrays to have nicer printouts with CHECK(...)
|
|
||||||
mutable std::unique_ptr<bool[]> m_derefed;
|
|
||||||
|
|
||||||
// We want to check which elements were dereferenced when iterating, so
|
|
||||||
// we can check whether iterator-using code traverses range correctly
|
|
||||||
template <bool is_const> class basic_iterator {
|
|
||||||
template <typename U>
|
|
||||||
using constify_t = std::conditional_t<is_const, std::add_const_t<U>, U>;
|
|
||||||
|
|
||||||
constify_t<with_mocked_iterator_access>* m_origin;
|
|
||||||
size_t m_origin_idx;
|
|
||||||
|
|
||||||
public:
|
|
||||||
using iterator_category = std::forward_iterator_tag;
|
|
||||||
using difference_type = std::ptrdiff_t;
|
|
||||||
using value_type = constify_t<T>;
|
|
||||||
using const_reference = typename std::vector<T>::const_reference;
|
|
||||||
using reference = typename std::vector<T>::reference;
|
|
||||||
using pointer = typename std::vector<T>::pointer;
|
|
||||||
|
|
||||||
basic_iterator( constify_t<with_mocked_iterator_access>* origin,
|
|
||||||
std::size_t origin_idx ):
|
|
||||||
m_origin{ origin }, m_origin_idx{ origin_idx } {}
|
|
||||||
|
|
||||||
friend bool operator==( basic_iterator lhs, basic_iterator rhs ) {
|
|
||||||
return lhs.m_origin == rhs.m_origin &&
|
|
||||||
lhs.m_origin_idx == rhs.m_origin_idx;
|
|
||||||
}
|
|
||||||
friend bool operator!=( basic_iterator lhs, basic_iterator rhs ) {
|
|
||||||
return !( lhs == rhs );
|
|
||||||
}
|
|
||||||
basic_iterator& operator++() {
|
|
||||||
++m_origin_idx;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
basic_iterator operator++( int ) {
|
|
||||||
auto tmp( *this );
|
|
||||||
++( *this );
|
|
||||||
return tmp;
|
|
||||||
}
|
|
||||||
const_reference operator*() const {
|
|
||||||
assert( m_origin_idx < m_origin->m_elements.size() && "Attempted to deref invalid position" );
|
|
||||||
m_origin->m_derefed[m_origin_idx] = true;
|
|
||||||
return m_origin->m_elements[m_origin_idx];
|
|
||||||
}
|
|
||||||
pointer operator->() const {
|
|
||||||
assert( m_origin_idx < m_origin->m_elements.size() && "Attempted to deref invalid position" );
|
|
||||||
return &m_origin->m_elements[m_origin_idx];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using iterator = basic_iterator<false>;
|
|
||||||
using const_iterator = basic_iterator<true>;
|
|
||||||
|
|
||||||
with_mocked_iterator_access( std::initializer_list<T> init ):
|
|
||||||
m_elements( init ),
|
|
||||||
m_derefed( std::make_unique<bool[]>( m_elements.size() ) ) {}
|
|
||||||
|
|
||||||
const_iterator begin() const { return { this, 0 }; }
|
|
||||||
const_iterator end() const { return { this, m_elements.size() }; }
|
|
||||||
iterator begin() { return { this, 0 }; }
|
|
||||||
iterator end() { return { this, m_elements.size() }; }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // end anon namespace
|
|
||||||
|
|
||||||
namespace Catch {
|
|
||||||
// make sure with_mocked_iterator_access is not considered a range by Catch,
|
|
||||||
// so that below StringMaker is used instead of the default one for ranges
|
|
||||||
template <typename T>
|
|
||||||
struct is_range<with_mocked_iterator_access<T>> : std::false_type {};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct StringMaker<with_mocked_iterator_access<T>> {
|
|
||||||
static std::string
|
|
||||||
convert( with_mocked_iterator_access<T> const& access ) {
|
|
||||||
// We have to avoid the type's iterators, because we check
|
|
||||||
// their use in tests
|
|
||||||
return ::Catch::Detail::stringify( access.m_elements );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace Catch
|
|
||||||
|
|
||||||
struct MoveOnlyTestElement {
|
struct MoveOnlyTestElement {
|
||||||
int num = 0;
|
int num = 0;
|
||||||
MoveOnlyTestElement(int n) :num(n) {}
|
MoveOnlyTestElement(int n) :num(n) {}
|
||||||
@ -287,16 +107,6 @@ namespace {
|
|||||||
bool empty() const { return false; }
|
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
|
} // end unnamed namespace
|
||||||
|
|
||||||
TEST_CASE("Basic use of the Empty range matcher", "[matchers][templated][empty]") {
|
TEST_CASE("Basic use of the Empty range matcher", "[matchers][templated][empty]") {
|
||||||
@ -346,17 +156,6 @@ namespace {
|
|||||||
return LessThanMatcher{ 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 {
|
struct has_size {
|
||||||
size_t size() const {
|
size_t size() const {
|
||||||
return 13;
|
return 13;
|
||||||
|
210
tests/SelfTest/helpers/range_test_helpers.hpp
Normal file
210
tests/SelfTest/helpers/range_test_helpers.hpp
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
|
||||||
|
// Copyright Catch2 Authors
|
||||||
|
// Distributed under the Boost Software License, Version 1.0.
|
||||||
|
// (See accompanying file LICENSE.txt or copy at
|
||||||
|
// https://www.boost.org/LICENSE_1_0.txt)
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
|
|
||||||
|
#ifndef CATCH_TEST_HELPERS_RANGE_TEST_HELPERS_HPP_INCLUDED
|
||||||
|
#define CATCH_TEST_HELPERS_RANGE_TEST_HELPERS_HPP_INCLUDED
|
||||||
|
|
||||||
|
#include <catch2/catch_tostring.hpp>
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace unrelated {
|
||||||
|
template <typename T>
|
||||||
|
class needs_ADL_begin {
|
||||||
|
std::vector<T> m_elements;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator = typename std::vector<T>::iterator;
|
||||||
|
using const_iterator = typename std::vector<T>::const_iterator;
|
||||||
|
|
||||||
|
needs_ADL_begin( std::initializer_list<T> init ): m_elements( init ) {}
|
||||||
|
|
||||||
|
const_iterator Begin() const { return m_elements.begin(); }
|
||||||
|
const_iterator End() const { return m_elements.end(); }
|
||||||
|
|
||||||
|
friend const_iterator begin( needs_ADL_begin const& lhs ) {
|
||||||
|
return lhs.Begin();
|
||||||
|
}
|
||||||
|
friend const_iterator end( needs_ADL_begin const& rhs ) {
|
||||||
|
return rhs.End();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ADL_empty {
|
||||||
|
bool Empty() const { return true; }
|
||||||
|
|
||||||
|
friend bool empty( ADL_empty e ) { return e.Empty(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ADL_size {
|
||||||
|
size_t sz() const { return 12; }
|
||||||
|
friend size_t size( ADL_size s ) { return s.sz(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace unrelated
|
||||||
|
|
||||||
|
#if defined( __clang__ )
|
||||||
|
# pragma clang diagnostic push
|
||||||
|
# pragma clang diagnostic ignored "-Wunused-function"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class has_different_begin_end_types {
|
||||||
|
// Using std::vector<T> leads to annoying issues when T is bool
|
||||||
|
// so we just use list because the perf is not critical and ugh.
|
||||||
|
std::list<T> m_elements;
|
||||||
|
|
||||||
|
// Different type for the "end" iterator
|
||||||
|
struct iterator_end {};
|
||||||
|
// Fake-ish forward iterator that only compares to a different type
|
||||||
|
class iterator {
|
||||||
|
using underlying_iter = typename std::list<T>::const_iterator;
|
||||||
|
underlying_iter m_start;
|
||||||
|
underlying_iter m_end;
|
||||||
|
|
||||||
|
public:
|
||||||
|
iterator( underlying_iter start, underlying_iter end ):
|
||||||
|
m_start( start ), m_end( end ) {}
|
||||||
|
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = T;
|
||||||
|
using const_reference = T const&;
|
||||||
|
using pointer = T const*;
|
||||||
|
|
||||||
|
friend bool operator==( iterator iter, iterator_end ) {
|
||||||
|
return iter.m_start == iter.m_end;
|
||||||
|
}
|
||||||
|
friend bool operator==(iterator lhs, iterator rhs) {
|
||||||
|
return lhs.m_start == rhs.m_start && lhs.m_end == rhs.m_end;
|
||||||
|
}
|
||||||
|
friend bool operator!=( iterator iter, iterator_end ) {
|
||||||
|
return iter.m_start != iter.m_end;
|
||||||
|
}
|
||||||
|
friend bool operator!=( iterator lhs, iterator rhs ) {
|
||||||
|
return !( lhs == rhs );
|
||||||
|
}
|
||||||
|
iterator& operator++() {
|
||||||
|
++m_start;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
iterator operator++( int ) {
|
||||||
|
auto tmp( *this );
|
||||||
|
++m_start;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
const_reference operator*() const { return *m_start; }
|
||||||
|
pointer operator->() const { return m_start; }
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit has_different_begin_end_types( std::initializer_list<T> init ):
|
||||||
|
m_elements( init ) {}
|
||||||
|
|
||||||
|
iterator begin() const { return { m_elements.begin(), m_elements.end() }; }
|
||||||
|
|
||||||
|
iterator_end end() const { return {}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined( __clang__ )
|
||||||
|
# pragma clang diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct with_mocked_iterator_access {
|
||||||
|
std::vector<T> m_elements;
|
||||||
|
|
||||||
|
// use plain arrays to have nicer printouts with CHECK(...)
|
||||||
|
mutable std::unique_ptr<bool[]> m_derefed;
|
||||||
|
|
||||||
|
// We want to check which elements were dereferenced when iterating, so
|
||||||
|
// we can check whether iterator-using code traverses range correctly
|
||||||
|
template <bool is_const>
|
||||||
|
class basic_iterator {
|
||||||
|
template <typename U>
|
||||||
|
using constify_t = std::conditional_t<is_const, std::add_const_t<U>, U>;
|
||||||
|
|
||||||
|
constify_t<with_mocked_iterator_access>* m_origin;
|
||||||
|
size_t m_origin_idx;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using value_type = constify_t<T>;
|
||||||
|
using const_reference = typename std::vector<T>::const_reference;
|
||||||
|
using reference = typename std::vector<T>::reference;
|
||||||
|
using pointer = typename std::vector<T>::pointer;
|
||||||
|
|
||||||
|
basic_iterator( constify_t<with_mocked_iterator_access>* origin,
|
||||||
|
std::size_t origin_idx ):
|
||||||
|
m_origin{ origin }, m_origin_idx{ origin_idx } {}
|
||||||
|
|
||||||
|
friend bool operator==( basic_iterator lhs, basic_iterator rhs ) {
|
||||||
|
return lhs.m_origin == rhs.m_origin &&
|
||||||
|
lhs.m_origin_idx == rhs.m_origin_idx;
|
||||||
|
}
|
||||||
|
friend bool operator!=( basic_iterator lhs, basic_iterator rhs ) {
|
||||||
|
return !( lhs == rhs );
|
||||||
|
}
|
||||||
|
basic_iterator& operator++() {
|
||||||
|
++m_origin_idx;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
basic_iterator operator++( int ) {
|
||||||
|
auto tmp( *this );
|
||||||
|
++( *this );
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
const_reference operator*() const {
|
||||||
|
assert( m_origin_idx < m_origin->m_elements.size() &&
|
||||||
|
"Attempted to deref invalid position" );
|
||||||
|
m_origin->m_derefed[m_origin_idx] = true;
|
||||||
|
return m_origin->m_elements[m_origin_idx];
|
||||||
|
}
|
||||||
|
pointer operator->() const {
|
||||||
|
assert( m_origin_idx < m_origin->m_elements.size() &&
|
||||||
|
"Attempted to deref invalid position" );
|
||||||
|
return &m_origin->m_elements[m_origin_idx];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using iterator = basic_iterator<false>;
|
||||||
|
using const_iterator = basic_iterator<true>;
|
||||||
|
|
||||||
|
with_mocked_iterator_access( std::initializer_list<T> init ):
|
||||||
|
m_elements( init ),
|
||||||
|
m_derefed( std::make_unique<bool[]>( m_elements.size() ) ) {}
|
||||||
|
|
||||||
|
const_iterator begin() const { return { this, 0 }; }
|
||||||
|
const_iterator end() const { return { this, m_elements.size() }; }
|
||||||
|
iterator begin() { return { this, 0 }; }
|
||||||
|
iterator end() { return { this, m_elements.size() }; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace Catch {
|
||||||
|
// make sure with_mocked_iterator_access is not considered a range by Catch,
|
||||||
|
// so that below StringMaker is used instead of the default one for ranges
|
||||||
|
template <typename T>
|
||||||
|
struct is_range<with_mocked_iterator_access<T>> : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct StringMaker<with_mocked_iterator_access<T>> {
|
||||||
|
static std::string
|
||||||
|
convert( with_mocked_iterator_access<T> const& access ) {
|
||||||
|
// We have to avoid the type's iterators, because we check
|
||||||
|
// their use in tests
|
||||||
|
return ::Catch::Detail::stringify( access.m_elements );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace Catch
|
||||||
|
|
||||||
|
#endif // CATCH_TEST_HELPERS_RANGE_TEST_HELPERS_HPP_INCLUDED
|
@ -9,6 +9,7 @@
|
|||||||
# Please keep these ordered alphabetically
|
# Please keep these ordered alphabetically
|
||||||
self_test_sources = files(
|
self_test_sources = files(
|
||||||
'SelfTest/helpers/parse_test_spec.cpp',
|
'SelfTest/helpers/parse_test_spec.cpp',
|
||||||
|
'SelfTest/IntrospectiveTests/Algorithms.tests.cpp',
|
||||||
'SelfTest/IntrospectiveTests/Clara.tests.cpp',
|
'SelfTest/IntrospectiveTests/Clara.tests.cpp',
|
||||||
'SelfTest/IntrospectiveTests/CmdLine.tests.cpp',
|
'SelfTest/IntrospectiveTests/CmdLine.tests.cpp',
|
||||||
'SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp',
|
'SelfTest/IntrospectiveTests/CmdLineHelpers.tests.cpp',
|
||||||
|
Loading…
Reference in New Issue
Block a user