diff --git a/include/internal/catch_matchers_vector.h b/include/internal/catch_matchers_vector.h index 84ffab87..833d7dc2 100644 --- a/include/internal/catch_matchers_vector.h +++ b/include/internal/catch_matchers_vector.h @@ -10,10 +10,33 @@ #include "catch_matchers.h" +#include + namespace Catch { namespace Matchers { namespace Vector { + namespace Detail { + template + size_t count(InputIterator first, InputIterator last, T const& item) { + size_t cnt = 0; + for (; first != last; ++first) { + if (*first == item) { + ++cnt; + } + } + return cnt; + } + template + bool contains(InputIterator first, InputIterator last, T const& item) { + for (; first != last; ++first) { + if (*first == item) { + return true; + } + } + return false; + } + } template struct ContainsElementMatcher : MatcherBase> { @@ -89,6 +112,46 @@ namespace Matchers { std::vector const& m_comparator; }; + template + struct UnorderedEqualsMatcher : MatcherBase> { + UnorderedEqualsMatcher(std::vector const& target) : m_target(target) {} + bool match(std::vector const& vec) const override { + // Note: This is a reimplementation of std::is_permutation, + // because I don't want to include inside the common path + if (m_target.size() != vec.size()) { + return false; + } + auto lfirst = m_target.begin(), llast = m_target.end(); + auto rfirst = vec.begin(), rlast = vec.end(); + // Cut common prefix to optimize checking of permuted parts + while (lfirst != llast && *lfirst != *rfirst) { + ++lfirst; ++rfirst; + } + if (lfirst == llast) { + return true; + } + + for (auto mid = lfirst; mid != llast; ++mid) { + // Skip already counted items + if (Detail::contains(lfirst, mid, *mid)) { + continue; + } + size_t num_vec = Detail::count(rfirst, rlast, *mid); + if (num_vec == 0 || Detail::count(lfirst, llast, *mid) != num_vec) { + return false; + } + } + + return true; + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + private: + std::vector const& m_target; + }; + } // namespace Vector // The following functions create the actual matcher objects. @@ -109,6 +172,11 @@ namespace Matchers { return Vector::EqualsMatcher( comparator ); } + template + Vector::UnorderedEqualsMatcher UnorderedEquals(std::vector const& target) { + return Vector::UnorderedEqualsMatcher(target); + } + } // namespace Matchers } // namespace Catch diff --git a/projects/SelfTest/Baselines/compact.sw.approved.txt b/projects/SelfTest/Baselines/compact.sw.approved.txt index a6e69d16..b205a13f 100644 --- a/projects/SelfTest/Baselines/compact.sw.approved.txt +++ b/projects/SelfTest/Baselines/compact.sw.approved.txt @@ -828,6 +828,10 @@ Matchers.tests.cpp:: passed: v, VectorContains(1) && VectorContains Matchers.tests.cpp:: passed: v, Equals(v) for: { 1, 2, 3 } Equals: { 1, 2, 3 } Matchers.tests.cpp:: passed: empty, Equals(empty) for: { } Equals: { } Matchers.tests.cpp:: passed: v, Equals(v2) for: { 1, 2, 3 } Equals: { 1, 2, 3 } +Matchers.tests.cpp:: passed: v, UnorderedEquals(v) for: { 1, 2, 3 } UnorderedEquals: { 1, 2, 3 } +Matchers.tests.cpp:: passed: empty, UnorderedEquals(empty) for: { } UnorderedEquals: { } +Matchers.tests.cpp:: passed: permuted, UnorderedEquals(v) for: { 1, 3, 2 } UnorderedEquals: { 1, 2, 3 } +Matchers.tests.cpp:: passed: permuted, UnorderedEquals(v) for: { 2, 3, 1 } UnorderedEquals: { 1, 2, 3 } Matchers.tests.cpp:: failed: v, VectorContains(-1) for: { 1, 2, 3 } Contains: -1 Matchers.tests.cpp:: failed: empty, VectorContains(1) for: { } Contains: 1 Matchers.tests.cpp:: failed: empty, Contains(v) for: { } Contains: { 1, 2, 3 } @@ -836,6 +840,10 @@ Matchers.tests.cpp:: failed: v, Equals(v2) for: { 1, 2, 3 } Equals: Matchers.tests.cpp:: failed: v2, Equals(v) for: { 1, 2 } Equals: { 1, 2, 3 } Matchers.tests.cpp:: failed: empty, Equals(v) for: { } Equals: { 1, 2, 3 } Matchers.tests.cpp:: failed: v, Equals(empty) for: { 1, 2, 3 } Equals: { } +Matchers.tests.cpp:: failed: v, UnorderedEquals(empty) for: { 1, 2, 3 } UnorderedEquals: { } +Matchers.tests.cpp:: failed: empty, UnorderedEquals(v) for: { } UnorderedEquals: { 1, 2, 3 } +Matchers.tests.cpp:: failed: permuted, UnorderedEquals(v) for: { 1, 3 } UnorderedEquals: { 1, 2, 3 } +Matchers.tests.cpp:: failed: permuted, UnorderedEquals(v) for: { 3, 1 } UnorderedEquals: { 1, 2, 3 } Exception.tests.cpp:: passed: thisThrows(), std::domain_error Exception.tests.cpp:: passed: thisDoesntThrow() Exception.tests.cpp:: passed: thisThrows() @@ -1023,5 +1031,5 @@ Misc.tests.cpp:: passed: v.size() == 5 for: 5 == 5 Misc.tests.cpp:: passed: v.capacity() >= 5 for: 5 >= 5 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: -Failed 50 test cases, failed 106 assertions. +Failed 50 test cases, failed 110 assertions. diff --git a/projects/SelfTest/Baselines/console.std.approved.txt b/projects/SelfTest/Baselines/console.std.approved.txt index eaee3ee1..d8087755 100644 --- a/projects/SelfTest/Baselines/console.std.approved.txt +++ b/projects/SelfTest/Baselines/console.std.approved.txt @@ -809,6 +809,33 @@ Matchers.tests.cpp:: FAILED: with expansion: { 1, 2, 3 } Equals: { } +------------------------------------------------------------------------------- +Vector matchers that fail + UnorderedEquals +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( v, UnorderedEquals(empty) ) +with expansion: + { 1, 2, 3 } UnorderedEquals: { } + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( empty, UnorderedEquals(v) ) +with expansion: + { } UnorderedEquals: { 1, 2, 3 } + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( permuted, UnorderedEquals(v) ) +with expansion: + { 1, 3 } UnorderedEquals: { 1, 2, 3 } + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( permuted, UnorderedEquals(v) ) +with expansion: + { 3, 1 } UnorderedEquals: { 1, 2, 3 } + ------------------------------------------------------------------------------- When unchecked exceptions are thrown directly they are always failures ------------------------------------------------------------------------------- @@ -1054,5 +1081,5 @@ with expansion: =============================================================================== test cases: 191 | 139 passed | 48 failed | 4 failed as expected -assertions: 963 | 839 passed | 103 failed | 21 failed as expected +assertions: 971 | 843 passed | 107 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/console.sw.approved.txt b/projects/SelfTest/Baselines/console.sw.approved.txt index 24c5a26f..c755c1f0 100644 --- a/projects/SelfTest/Baselines/console.sw.approved.txt +++ b/projects/SelfTest/Baselines/console.sw.approved.txt @@ -6480,6 +6480,37 @@ PASSED: with expansion: { 1, 2, 3 } Equals: { 1, 2, 3 } +------------------------------------------------------------------------------- +Vector matchers + UnorderedEquals +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: +PASSED: + CHECK_THAT( v, UnorderedEquals(v) ) +with expansion: + { 1, 2, 3 } UnorderedEquals: { 1, 2, 3 } + +Matchers.tests.cpp:: +PASSED: + CHECK_THAT( empty, UnorderedEquals(empty) ) +with expansion: + { } UnorderedEquals: { } + +Matchers.tests.cpp:: +PASSED: + REQUIRE_THAT( permuted, UnorderedEquals(v) ) +with expansion: + { 1, 3, 2 } UnorderedEquals: { 1, 2, 3 } + +Matchers.tests.cpp:: +PASSED: + REQUIRE_THAT( permuted, UnorderedEquals(v) ) +with expansion: + { 2, 3, 1 } UnorderedEquals: { 1, 2, 3 } + ------------------------------------------------------------------------------- Vector matchers that fail Contains (element) @@ -6541,6 +6572,33 @@ Matchers.tests.cpp:: FAILED: with expansion: { 1, 2, 3 } Equals: { } +------------------------------------------------------------------------------- +Vector matchers that fail + UnorderedEquals +------------------------------------------------------------------------------- +Matchers.tests.cpp: +............................................................................... + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( v, UnorderedEquals(empty) ) +with expansion: + { 1, 2, 3 } UnorderedEquals: { } + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( empty, UnorderedEquals(v) ) +with expansion: + { } UnorderedEquals: { 1, 2, 3 } + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( permuted, UnorderedEquals(v) ) +with expansion: + { 1, 3 } UnorderedEquals: { 1, 2, 3 } + +Matchers.tests.cpp:: FAILED: + CHECK_THAT( permuted, UnorderedEquals(v) ) +with expansion: + { 3, 1 } UnorderedEquals: { 1, 2, 3 } + ------------------------------------------------------------------------------- When checked exceptions are thrown they can be expected or unexpected ------------------------------------------------------------------------------- @@ -8057,5 +8115,5 @@ PASSED: =============================================================================== test cases: 191 | 137 passed | 50 failed | 4 failed as expected -assertions: 962 | 835 passed | 106 failed | 21 failed as expected +assertions: 970 | 839 passed | 110 failed | 21 failed as expected diff --git a/projects/SelfTest/Baselines/junit.sw.approved.txt b/projects/SelfTest/Baselines/junit.sw.approved.txt index 4436121f..11baab4d 100644 --- a/projects/SelfTest/Baselines/junit.sw.approved.txt +++ b/projects/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -608,6 +608,7 @@ Exception.tests.cpp: + Matchers.tests.cpp: @@ -635,6 +636,20 @@ Matchers.tests.cpp: Matchers.tests.cpp: +Matchers.tests.cpp: + + + + +Matchers.tests.cpp: + + +Matchers.tests.cpp: + + +Matchers.tests.cpp: + + Matchers.tests.cpp: diff --git a/projects/SelfTest/Baselines/xml.sw.approved.txt b/projects/SelfTest/Baselines/xml.sw.approved.txt index 73fc131e..99274e0f 100644 --- a/projects/SelfTest/Baselines/xml.sw.approved.txt +++ b/projects/SelfTest/Baselines/xml.sw.approved.txt @@ -7377,6 +7377,41 @@ Message from section two +
+ + + v, UnorderedEquals(v) + + + { 1, 2, 3 } UnorderedEquals: { 1, 2, 3 } + + + + + empty, UnorderedEquals(empty) + + + { } UnorderedEquals: { } + + + + + permuted, UnorderedEquals(v) + + + { 1, 3, 2 } UnorderedEquals: { 1, 2, 3 } + + + + + permuted, UnorderedEquals(v) + + + { 2, 3, 1 } UnorderedEquals: { 1, 2, 3 } + + + +
@@ -7453,6 +7488,41 @@ Message from section two +
+ + + v, UnorderedEquals(empty) + + + { 1, 2, 3 } UnorderedEquals: { } + + + + + empty, UnorderedEquals(v) + + + { } UnorderedEquals: { 1, 2, 3 } + + + + + permuted, UnorderedEquals(v) + + + { 1, 3 } UnorderedEquals: { 1, 2, 3 } + + + + + permuted, UnorderedEquals(v) + + + { 3, 1 } UnorderedEquals: { 1, 2, 3 } + + + +
@@ -8968,7 +9038,7 @@ loose text artifact - + - + diff --git a/projects/SelfTest/UsageTests/Matchers.tests.cpp b/projects/SelfTest/UsageTests/Matchers.tests.cpp index 8ffa995c..3c20caa1 100644 --- a/projects/SelfTest/UsageTests/Matchers.tests.cpp +++ b/projects/SelfTest/UsageTests/Matchers.tests.cpp @@ -9,6 +9,7 @@ #include "catch.hpp" #include +#include #ifdef __clang__ #pragma clang diagnostic push @@ -216,6 +217,17 @@ namespace { namespace MatchersTests { v2.push_back(3); CHECK_THAT(v, Equals(v2)); } + SECTION("UnorderedEquals") { + CHECK_THAT(v, UnorderedEquals(v)); + CHECK_THAT(empty, UnorderedEquals(empty)); + + auto permuted = v; + std::next_permutation(begin(permuted), end(permuted)); + REQUIRE_THAT(permuted, UnorderedEquals(v)); + + std::reverse(begin(permuted), end(permuted)); + REQUIRE_THAT(permuted, UnorderedEquals(v)); + } } TEST_CASE("Vector matchers that fail", "[matchers][vector][.][failing]") { @@ -247,6 +259,18 @@ namespace { namespace MatchersTests { CHECK_THAT(empty, Equals(v)); CHECK_THAT(v, Equals(empty)); } + SECTION("UnorderedEquals") { + CHECK_THAT(v, UnorderedEquals(empty)); + CHECK_THAT(empty, UnorderedEquals(v)); + + auto permuted = v; + std::next_permutation(begin(permuted), end(permuted)); + permuted.pop_back(); + CHECK_THAT(permuted, UnorderedEquals(v)); + + std::reverse(begin(permuted), end(permuted)); + CHECK_THAT(permuted, UnorderedEquals(v)); + } } TEST_CASE("Exception matchers that succeed", "[matchers][exceptions][!throws]") {