From 4ec4cd23f486bf70efcc5d2caa40f24368f752e3 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 29 Jun 2021 21:55:02 -0400 Subject: [PATCH] Googletest export Implement 'Contains(e).Times(n)' matcher modifier which allows to test for arbitrary occurrences including absence with Times(0). PiperOrigin-RevId: 382210276 --- docs/reference/matchers.md | 1 + googlemock/include/gmock/gmock-matchers.h | 128 +++++++++++++++++++++- googlemock/test/gmock-matchers_test.cc | 97 ++++++++++++++-- 3 files changed, 209 insertions(+), 17 deletions(-) diff --git a/docs/reference/matchers.md b/docs/reference/matchers.md index 8697060d..0d8f81be 100644 --- a/docs/reference/matchers.md +++ b/docs/reference/matchers.md @@ -116,6 +116,7 @@ messages, you can use: | `BeginEndDistanceIs(m)` | `argument` is a container whose `begin()` and `end()` iterators are separated by a number of increments matching `m`. E.g. `BeginEndDistanceIs(2)` or `BeginEndDistanceIs(Lt(2))`. For containers that define a `size()` method, `SizeIs(m)` may be more efficient. | | `ContainerEq(container)` | The same as `Eq(container)` except that the failure message also includes which elements are in one container but not the other. | | `Contains(e)` | `argument` contains an element that matches `e`, which can be either a value or a matcher. | +| `Contains(e).Times(n)` | `argument` contains elements that match `e`, which can be either a value or a matcher, and the number of matches is `n`, which can be either a value or a matcher. Unlike the plain `Contains` and `Each` this allows to check for arbitrary occurrences including testing for absence with `Contains(e).Times(0)`. | | `Each(e)` | `argument` is a container where *every* element matches `e`, which can be either a value or a matcher. | | `ElementsAre(e0, e1, ..., en)` | `argument` has `n + 1` elements, where the *i*-th element matches `ei`, which can be a value or a matcher. | | `ElementsAreArray({e0, e1, ..., en})`, `ElementsAreArray(a_container)`, `ElementsAreArray(begin, end)`, `ElementsAreArray(array)`, or `ElementsAreArray(array, count)` | The same as `ElementsAre()` except that the expected element values/matchers come from an initializer list, STL-style container, iterator range, or C-style array. | diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index be174b7a..e1a76063 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -2581,7 +2581,7 @@ class PointwiseMatcher { StringMatchResultListener inner_listener; // Create InnerMatcherArg as a temporarily object to avoid it outlives // *left and *right. Dereference or the conversion to `const T&` may - // return temp objects, e.g for vector. + // return temp objects, e.g. for vector. if (!mono_tuple_matcher_.MatchAndExplain( InnerMatcherArg(ImplicitCast_(*left), ImplicitCast_(*right)), @@ -2653,6 +2653,54 @@ class QuantifierMatcherImpl : public MatcherInterface { return all_elements_should_match; } + bool MatchAndExplainImpl(const Matcher& count_matcher, + Container container, + MatchResultListener* listener) const { + StlContainerReference stl_container = View::ConstReference(container); + size_t i = 0; + std::vector match_elements; + for (auto it = stl_container.begin(); it != stl_container.end(); + ++it, ++i) { + StringMatchResultListener inner_listener; + const bool matches = inner_matcher_.MatchAndExplain(*it, &inner_listener); + if (matches) { + match_elements.push_back(i); + } + } + if (listener->IsInterested()) { + if (match_elements.empty()) { + *listener << "has no element that matches"; + } else if (match_elements.size() == 1) { + *listener << "whose element #" << match_elements[0] << " matches"; + } else { + *listener << "whose elements ("; + std::string sep = ""; + for (size_t e : match_elements) { + *listener << sep << e; + sep = ", "; + } + *listener << ") match"; + } + } + StringMatchResultListener count_listener; + if (count_matcher.MatchAndExplain(match_elements.size(), &count_listener)) { + *listener << " and whose match quantity of " << match_elements.size() + << " matches"; + PrintIfNotEmpty(count_listener.str(), listener->stream()); + return true; + } else { + if (match_elements.empty()) { + *listener << " and"; + } else { + *listener << " but"; + } + *listener << " whose match quantity of " << match_elements.size() + << " does not match"; + PrintIfNotEmpty(count_listener.str(), listener->stream()); + return false; + } + } + protected: const Matcher inner_matcher_; }; @@ -2709,6 +2757,58 @@ class EachMatcherImpl : public QuantifierMatcherImpl { } }; +// Implements Contains(element_matcher).Times(n) for the given argument type +// Container. +template +class ContainsTimesMatcherImpl : public QuantifierMatcherImpl { + public: + template + explicit ContainsTimesMatcherImpl(InnerMatcher inner_matcher, + Matcher count_matcher) + : QuantifierMatcherImpl(inner_matcher), + count_matcher_(std::move(count_matcher)) {} + + void DescribeTo(::std::ostream* os) const override { + *os << "quantity of elements that match "; + this->inner_matcher_.DescribeTo(os); + *os << " "; + count_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "quantity of elements that match "; + this->inner_matcher_.DescribeTo(os); + *os << " "; + count_matcher_.DescribeNegationTo(os); + } + + bool MatchAndExplain(Container container, + MatchResultListener* listener) const override { + return this->MatchAndExplainImpl(count_matcher_, container, listener); + } + + private: + const Matcher count_matcher_; +}; + +// Implements polymorphic Contains(element_matcher).Times(n). +template +class ContainsTimesMatcher { + public: + explicit ContainsTimesMatcher(M m, Matcher count_matcher) + : inner_matcher_(m), count_matcher_(std::move(count_matcher)) {} + + template + operator Matcher() const { // NOLINT + return Matcher(new ContainsTimesMatcherImpl( + inner_matcher_, count_matcher_)); + } + + private: + const M inner_matcher_; + const Matcher count_matcher_; +}; + // Implements polymorphic Contains(element_matcher). template class ContainsMatcher { @@ -2716,11 +2816,15 @@ class ContainsMatcher { explicit ContainsMatcher(M m) : inner_matcher_(m) {} template - operator Matcher() const { + operator Matcher() const { // NOLINT return Matcher( new ContainsMatcherImpl(inner_matcher_)); } + ContainsTimesMatcher Times(Matcher count_matcher) const { + return ContainsTimesMatcher(inner_matcher_, std::move(count_matcher)); + } + private: const M inner_matcher_; }; @@ -2732,7 +2836,7 @@ class EachMatcher { explicit EachMatcher(M m) : inner_matcher_(m) {} template - operator Matcher() const { + operator Matcher() const { // NOLINT return Matcher( new EachMatcherImpl(inner_matcher_)); } @@ -4615,7 +4719,6 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher, return UnorderedPointwise(tuple2_matcher, std::vector(rhs)); } - // Matches an STL-style container or a native array that contains at // least one element matching the given value or matcher. // @@ -4625,7 +4728,7 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher, // page_ids.insert(1); // EXPECT_THAT(page_ids, Contains(1)); // EXPECT_THAT(page_ids, Contains(Gt(2))); -// EXPECT_THAT(page_ids, Not(Contains(4))); +// EXPECT_THAT(page_ids, Not(Contains(4))); // See below for Times(0) // // ::std::map page_lengths; // page_lengths[1] = 100; @@ -4634,6 +4737,19 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher, // // const char* user_ids[] = { "joe", "mike", "tom" }; // EXPECT_THAT(user_ids, Contains(Eq(::std::string("tom")))); +// +// The matcher supports a modifier `Times` that allows to check for arbitrary +// occurrences including testing for absence with Times(0). +// +// Examples: +// ::std::vector ids; +// ids.insert(1); +// ids.insert(1); +// ids.insert(3); +// EXPECT_THAT(ids, Contains(1).Times(2)); // 1 occurs 2 times +// EXPECT_THAT(ids, Contains(2).Times(0)); // 2 is not present +// EXPECT_THAT(ids, Contains(3).Times(Ge(1))); // 3 occurs at least once + template inline internal::ContainsMatcher Contains(M matcher) { return internal::ContainsMatcher(matcher); @@ -4760,7 +4876,7 @@ inline internal::UnorderedElementsAreArrayMatcher IsSubsetOf( // Matches an STL-style container or a native array that contains only // elements matching the given value or matcher. // -// Each(m) is semantically equivalent to Not(Contains(Not(m))). Only +// Each(m) is semantically equivalent to `Not(Contains(Not(m)))`. Only // the messages are different. // // Examples: diff --git a/googlemock/test/gmock-matchers_test.cc b/googlemock/test/gmock-matchers_test.cc index 1f48a76c..e7ce97c6 100644 --- a/googlemock/test/gmock-matchers_test.cc +++ b/googlemock/test/gmock-matchers_test.cc @@ -114,31 +114,32 @@ std::vector> MakeUniquePtrs(const std::vector& ints) { } // For testing ExplainMatchResultTo(). -class GreaterThanMatcher : public MatcherInterface { +template +class GreaterThanMatcher : public MatcherInterface { public: - explicit GreaterThanMatcher(int rhs) : rhs_(rhs) {} + explicit GreaterThanMatcher(T rhs) : rhs_(rhs) {} void DescribeTo(ostream* os) const override { *os << "is > " << rhs_; } - bool MatchAndExplain(int lhs, MatchResultListener* listener) const override { - const int diff = lhs - rhs_; - if (diff > 0) { - *listener << "which is " << diff << " more than " << rhs_; - } else if (diff == 0) { + bool MatchAndExplain(T lhs, MatchResultListener* listener) const override { + if (lhs > rhs_) { + *listener << "which is " << (lhs - rhs_) << " more than " << rhs_; + } else if (lhs == rhs_) { *listener << "which is the same as " << rhs_; } else { - *listener << "which is " << -diff << " less than " << rhs_; + *listener << "which is " << (rhs_ - lhs) << " less than " << rhs_; } return lhs > rhs_; } private: - int rhs_; + const T rhs_; }; -Matcher GreaterThan(int n) { - return MakeMatcher(new GreaterThanMatcher(n)); +template +Matcher GreaterThan(T n) { + return MakeMatcher(new GreaterThanMatcher(n)); } std::string OfType(const std::string& type_name) { @@ -8023,6 +8024,7 @@ TEST(ContainsTest, ListMatchesWhenElementIsInContainer) { some_list.push_back(3); some_list.push_back(1); some_list.push_back(2); + some_list.push_back(3); EXPECT_THAT(some_list, Contains(1)); EXPECT_THAT(some_list, Contains(Gt(2.5))); EXPECT_THAT(some_list, Contains(Eq(2.0f))); @@ -8147,6 +8149,79 @@ TEST(ContainsTest, WorksForTwoDimensionalNativeArray) { EXPECT_THAT(a, Contains(Not(Contains(5)))); } +// Tests Contains().Times(). + +TEST(ContainsTimes, ListMatchesWhenElementQuantityMatches) { + list some_list; + some_list.push_back(3); + some_list.push_back(1); + some_list.push_back(2); + some_list.push_back(3); + EXPECT_THAT(some_list, Contains(3).Times(2)); + EXPECT_THAT(some_list, Contains(2).Times(1)); + EXPECT_THAT(some_list, Contains(Ge(2)).Times(3)); + EXPECT_THAT(some_list, Contains(Ge(2)).Times(Gt(2))); + EXPECT_THAT(some_list, Contains(4).Times(0)); + EXPECT_THAT(some_list, Contains(_).Times(4)); + EXPECT_THAT(some_list, Not(Contains(5).Times(1))); + EXPECT_THAT(some_list, Contains(5).Times(_)); // Times(_) always matches + EXPECT_THAT(some_list, Not(Contains(3).Times(1))); + EXPECT_THAT(some_list, Contains(3).Times(Not(1))); + EXPECT_THAT(list{}, Not(Contains(_))); +} + +TEST(ContainsTimes, ExplainsMatchResultCorrectly) { + const int a[2] = {1, 2}; + Matcher m = Contains(2).Times(3); + EXPECT_EQ( + "whose element #1 matches but whose match quantity of 1 does not match", + Explain(m, a)); + + m = Contains(3).Times(0); + EXPECT_EQ("has no element that matches and whose match quantity of 0 matches", + Explain(m, a)); + + m = Contains(3).Times(4); + EXPECT_EQ( + "has no element that matches and whose match quantity of 0 does not " + "match", + Explain(m, a)); + + m = Contains(2).Times(4); + EXPECT_EQ( + "whose element #1 matches but whose match quantity of 1 does not " + "match", + Explain(m, a)); + + m = Contains(GreaterThan(0)).Times(2); + EXPECT_EQ("whose elements (0, 1) match and whose match quantity of 2 matches", + Explain(m, a)); + + m = Contains(GreaterThan(10)).Times(Gt(1)); + EXPECT_EQ( + "has no element that matches and whose match quantity of 0 does not " + "match", + Explain(m, a)); + + m = Contains(GreaterThan(0)).Times(GreaterThan(5)); + EXPECT_EQ( + "whose elements (0, 1) match but whose match quantity of 2 does not " + "match, which is 3 less than 5", + Explain(m, a)); +} + +TEST(ContainsTimes, DescribesItselfCorrectly) { + Matcher> m = Contains(1).Times(2); + EXPECT_EQ("quantity of elements that match is equal to 1 is equal to 2", + Describe(m)); + + Matcher> m2 = Not(m); + EXPECT_EQ("quantity of elements that match is equal to 1 isn't equal to 2", + Describe(m2)); +} + +// Tests AllOfArray() + TEST(AllOfArrayTest, BasicForms) { // Iterator std::vector v0{};