Skip to content

Commit 4ec4cd2

Browse files
Abseil TeamAndy Soffer
Abseil Team
authored and
Andy Soffer
committed
Googletest export
Implement 'Contains(e).Times(n)' matcher modifier which allows to test for arbitrary occurrences including absence with Times(0). PiperOrigin-RevId: 382210276
1 parent 5f97ce4 commit 4ec4cd2

File tree

3 files changed

+209
-17
lines changed

3 files changed

+209
-17
lines changed

docs/reference/matchers.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ messages, you can use:
116116
| `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. |
117117
| `ContainerEq(container)` | The same as `Eq(container)` except that the failure message also includes which elements are in one container but not the other. |
118118
| `Contains(e)` | `argument` contains an element that matches `e`, which can be either a value or a matcher. |
119+
| `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)`. |
119120
| `Each(e)` | `argument` is a container where *every* element matches `e`, which can be either a value or a matcher. |
120121
| `ElementsAre(e0, e1, ..., en)` | `argument` has `n + 1` elements, where the *i*-th element matches `ei`, which can be a value or a matcher. |
121122
| `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. |

googlemock/include/gmock/gmock-matchers.h

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,7 +2581,7 @@ class PointwiseMatcher {
25812581
StringMatchResultListener inner_listener;
25822582
// Create InnerMatcherArg as a temporarily object to avoid it outlives
25832583
// *left and *right. Dereference or the conversion to `const T&` may
2584-
// return temp objects, e.g for vector<bool>.
2584+
// return temp objects, e.g. for vector<bool>.
25852585
if (!mono_tuple_matcher_.MatchAndExplain(
25862586
InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left),
25872587
ImplicitCast_<const RhsValue&>(*right)),
@@ -2653,6 +2653,54 @@ class QuantifierMatcherImpl : public MatcherInterface<Container> {
26532653
return all_elements_should_match;
26542654
}
26552655

2656+
bool MatchAndExplainImpl(const Matcher<size_t>& count_matcher,
2657+
Container container,
2658+
MatchResultListener* listener) const {
2659+
StlContainerReference stl_container = View::ConstReference(container);
2660+
size_t i = 0;
2661+
std::vector<size_t> match_elements;
2662+
for (auto it = stl_container.begin(); it != stl_container.end();
2663+
++it, ++i) {
2664+
StringMatchResultListener inner_listener;
2665+
const bool matches = inner_matcher_.MatchAndExplain(*it, &inner_listener);
2666+
if (matches) {
2667+
match_elements.push_back(i);
2668+
}
2669+
}
2670+
if (listener->IsInterested()) {
2671+
if (match_elements.empty()) {
2672+
*listener << "has no element that matches";
2673+
} else if (match_elements.size() == 1) {
2674+
*listener << "whose element #" << match_elements[0] << " matches";
2675+
} else {
2676+
*listener << "whose elements (";
2677+
std::string sep = "";
2678+
for (size_t e : match_elements) {
2679+
*listener << sep << e;
2680+
sep = ", ";
2681+
}
2682+
*listener << ") match";
2683+
}
2684+
}
2685+
StringMatchResultListener count_listener;
2686+
if (count_matcher.MatchAndExplain(match_elements.size(), &count_listener)) {
2687+
*listener << " and whose match quantity of " << match_elements.size()
2688+
<< " matches";
2689+
PrintIfNotEmpty(count_listener.str(), listener->stream());
2690+
return true;
2691+
} else {
2692+
if (match_elements.empty()) {
2693+
*listener << " and";
2694+
} else {
2695+
*listener << " but";
2696+
}
2697+
*listener << " whose match quantity of " << match_elements.size()
2698+
<< " does not match";
2699+
PrintIfNotEmpty(count_listener.str(), listener->stream());
2700+
return false;
2701+
}
2702+
}
2703+
26562704
protected:
26572705
const Matcher<const Element&> inner_matcher_;
26582706
};
@@ -2709,18 +2757,74 @@ class EachMatcherImpl : public QuantifierMatcherImpl<Container> {
27092757
}
27102758
};
27112759

2760+
// Implements Contains(element_matcher).Times(n) for the given argument type
2761+
// Container.
2762+
template <typename Container>
2763+
class ContainsTimesMatcherImpl : public QuantifierMatcherImpl<Container> {
2764+
public:
2765+
template <typename InnerMatcher>
2766+
explicit ContainsTimesMatcherImpl(InnerMatcher inner_matcher,
2767+
Matcher<size_t> count_matcher)
2768+
: QuantifierMatcherImpl<Container>(inner_matcher),
2769+
count_matcher_(std::move(count_matcher)) {}
2770+
2771+
void DescribeTo(::std::ostream* os) const override {
2772+
*os << "quantity of elements that match ";
2773+
this->inner_matcher_.DescribeTo(os);
2774+
*os << " ";
2775+
count_matcher_.DescribeTo(os);
2776+
}
2777+
2778+
void DescribeNegationTo(::std::ostream* os) const override {
2779+
*os << "quantity of elements that match ";
2780+
this->inner_matcher_.DescribeTo(os);
2781+
*os << " ";
2782+
count_matcher_.DescribeNegationTo(os);
2783+
}
2784+
2785+
bool MatchAndExplain(Container container,
2786+
MatchResultListener* listener) const override {
2787+
return this->MatchAndExplainImpl(count_matcher_, container, listener);
2788+
}
2789+
2790+
private:
2791+
const Matcher<size_t> count_matcher_;
2792+
};
2793+
2794+
// Implements polymorphic Contains(element_matcher).Times(n).
2795+
template <typename M>
2796+
class ContainsTimesMatcher {
2797+
public:
2798+
explicit ContainsTimesMatcher(M m, Matcher<size_t> count_matcher)
2799+
: inner_matcher_(m), count_matcher_(std::move(count_matcher)) {}
2800+
2801+
template <typename Container>
2802+
operator Matcher<Container>() const { // NOLINT
2803+
return Matcher<Container>(new ContainsTimesMatcherImpl<const Container&>(
2804+
inner_matcher_, count_matcher_));
2805+
}
2806+
2807+
private:
2808+
const M inner_matcher_;
2809+
const Matcher<size_t> count_matcher_;
2810+
};
2811+
27122812
// Implements polymorphic Contains(element_matcher).
27132813
template <typename M>
27142814
class ContainsMatcher {
27152815
public:
27162816
explicit ContainsMatcher(M m) : inner_matcher_(m) {}
27172817

27182818
template <typename Container>
2719-
operator Matcher<Container>() const {
2819+
operator Matcher<Container>() const { // NOLINT
27202820
return Matcher<Container>(
27212821
new ContainsMatcherImpl<const Container&>(inner_matcher_));
27222822
}
27232823

2824+
ContainsTimesMatcher<M> Times(Matcher<size_t> count_matcher) const {
2825+
return ContainsTimesMatcher<M>(inner_matcher_, std::move(count_matcher));
2826+
}
2827+
27242828
private:
27252829
const M inner_matcher_;
27262830
};
@@ -2732,7 +2836,7 @@ class EachMatcher {
27322836
explicit EachMatcher(M m) : inner_matcher_(m) {}
27332837

27342838
template <typename Container>
2735-
operator Matcher<Container>() const {
2839+
operator Matcher<Container>() const { // NOLINT
27362840
return Matcher<Container>(
27372841
new EachMatcherImpl<const Container&>(inner_matcher_));
27382842
}
@@ -4615,7 +4719,6 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
46154719
return UnorderedPointwise(tuple2_matcher, std::vector<T>(rhs));
46164720
}
46174721

4618-
46194722
// Matches an STL-style container or a native array that contains at
46204723
// least one element matching the given value or matcher.
46214724
//
@@ -4625,7 +4728,7 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
46254728
// page_ids.insert(1);
46264729
// EXPECT_THAT(page_ids, Contains(1));
46274730
// EXPECT_THAT(page_ids, Contains(Gt(2)));
4628-
// EXPECT_THAT(page_ids, Not(Contains(4)));
4731+
// EXPECT_THAT(page_ids, Not(Contains(4))); // See below for Times(0)
46294732
//
46304733
// ::std::map<int, size_t> page_lengths;
46314734
// page_lengths[1] = 100;
@@ -4634,6 +4737,19 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
46344737
//
46354738
// const char* user_ids[] = { "joe", "mike", "tom" };
46364739
// EXPECT_THAT(user_ids, Contains(Eq(::std::string("tom"))));
4740+
//
4741+
// The matcher supports a modifier `Times` that allows to check for arbitrary
4742+
// occurrences including testing for absence with Times(0).
4743+
//
4744+
// Examples:
4745+
// ::std::vector<int> ids;
4746+
// ids.insert(1);
4747+
// ids.insert(1);
4748+
// ids.insert(3);
4749+
// EXPECT_THAT(ids, Contains(1).Times(2)); // 1 occurs 2 times
4750+
// EXPECT_THAT(ids, Contains(2).Times(0)); // 2 is not present
4751+
// EXPECT_THAT(ids, Contains(3).Times(Ge(1))); // 3 occurs at least once
4752+
46374753
template <typename M>
46384754
inline internal::ContainsMatcher<M> Contains(M matcher) {
46394755
return internal::ContainsMatcher<M>(matcher);
@@ -4760,7 +4876,7 @@ inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf(
47604876
// Matches an STL-style container or a native array that contains only
47614877
// elements matching the given value or matcher.
47624878
//
4763-
// Each(m) is semantically equivalent to Not(Contains(Not(m))). Only
4879+
// Each(m) is semantically equivalent to `Not(Contains(Not(m)))`. Only
47644880
// the messages are different.
47654881
//
47664882
// Examples:

googlemock/test/gmock-matchers_test.cc

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,31 +114,32 @@ std::vector<std::unique_ptr<int>> MakeUniquePtrs(const std::vector<int>& ints) {
114114
}
115115

116116
// For testing ExplainMatchResultTo().
117-
class GreaterThanMatcher : public MatcherInterface<int> {
117+
template <typename T = int>
118+
class GreaterThanMatcher : public MatcherInterface<T> {
118119
public:
119-
explicit GreaterThanMatcher(int rhs) : rhs_(rhs) {}
120+
explicit GreaterThanMatcher(T rhs) : rhs_(rhs) {}
120121

121122
void DescribeTo(ostream* os) const override { *os << "is > " << rhs_; }
122123

123-
bool MatchAndExplain(int lhs, MatchResultListener* listener) const override {
124-
const int diff = lhs - rhs_;
125-
if (diff > 0) {
126-
*listener << "which is " << diff << " more than " << rhs_;
127-
} else if (diff == 0) {
124+
bool MatchAndExplain(T lhs, MatchResultListener* listener) const override {
125+
if (lhs > rhs_) {
126+
*listener << "which is " << (lhs - rhs_) << " more than " << rhs_;
127+
} else if (lhs == rhs_) {
128128
*listener << "which is the same as " << rhs_;
129129
} else {
130-
*listener << "which is " << -diff << " less than " << rhs_;
130+
*listener << "which is " << (rhs_ - lhs) << " less than " << rhs_;
131131
}
132132

133133
return lhs > rhs_;
134134
}
135135

136136
private:
137-
int rhs_;
137+
const T rhs_;
138138
};
139139

140-
Matcher<int> GreaterThan(int n) {
141-
return MakeMatcher(new GreaterThanMatcher(n));
140+
template <typename T>
141+
Matcher<T> GreaterThan(T n) {
142+
return MakeMatcher(new GreaterThanMatcher<T>(n));
142143
}
143144

144145
std::string OfType(const std::string& type_name) {
@@ -8023,6 +8024,7 @@ TEST(ContainsTest, ListMatchesWhenElementIsInContainer) {
80238024
some_list.push_back(3);
80248025
some_list.push_back(1);
80258026
some_list.push_back(2);
8027+
some_list.push_back(3);
80268028
EXPECT_THAT(some_list, Contains(1));
80278029
EXPECT_THAT(some_list, Contains(Gt(2.5)));
80288030
EXPECT_THAT(some_list, Contains(Eq(2.0f)));
@@ -8147,6 +8149,79 @@ TEST(ContainsTest, WorksForTwoDimensionalNativeArray) {
81478149
EXPECT_THAT(a, Contains(Not(Contains(5))));
81488150
}
81498151

8152+
// Tests Contains().Times().
8153+
8154+
TEST(ContainsTimes, ListMatchesWhenElementQuantityMatches) {
8155+
list<int> some_list;
8156+
some_list.push_back(3);
8157+
some_list.push_back(1);
8158+
some_list.push_back(2);
8159+
some_list.push_back(3);
8160+
EXPECT_THAT(some_list, Contains(3).Times(2));
8161+
EXPECT_THAT(some_list, Contains(2).Times(1));
8162+
EXPECT_THAT(some_list, Contains(Ge(2)).Times(3));
8163+
EXPECT_THAT(some_list, Contains(Ge(2)).Times(Gt(2)));
8164+
EXPECT_THAT(some_list, Contains(4).Times(0));
8165+
EXPECT_THAT(some_list, Contains(_).Times(4));
8166+
EXPECT_THAT(some_list, Not(Contains(5).Times(1)));
8167+
EXPECT_THAT(some_list, Contains(5).Times(_)); // Times(_) always matches
8168+
EXPECT_THAT(some_list, Not(Contains(3).Times(1)));
8169+
EXPECT_THAT(some_list, Contains(3).Times(Not(1)));
8170+
EXPECT_THAT(list<int>{}, Not(Contains(_)));
8171+
}
8172+
8173+
TEST(ContainsTimes, ExplainsMatchResultCorrectly) {
8174+
const int a[2] = {1, 2};
8175+
Matcher<const int(&)[2]> m = Contains(2).Times(3);
8176+
EXPECT_EQ(
8177+
"whose element #1 matches but whose match quantity of 1 does not match",
8178+
Explain(m, a));
8179+
8180+
m = Contains(3).Times(0);
8181+
EXPECT_EQ("has no element that matches and whose match quantity of 0 matches",
8182+
Explain(m, a));
8183+
8184+
m = Contains(3).Times(4);
8185+
EXPECT_EQ(
8186+
"has no element that matches and whose match quantity of 0 does not "
8187+
"match",
8188+
Explain(m, a));
8189+
8190+
m = Contains(2).Times(4);
8191+
EXPECT_EQ(
8192+
"whose element #1 matches but whose match quantity of 1 does not "
8193+
"match",
8194+
Explain(m, a));
8195+
8196+
m = Contains(GreaterThan(0)).Times(2);
8197+
EXPECT_EQ("whose elements (0, 1) match and whose match quantity of 2 matches",
8198+
Explain(m, a));
8199+
8200+
m = Contains(GreaterThan(10)).Times(Gt(1));
8201+
EXPECT_EQ(
8202+
"has no element that matches and whose match quantity of 0 does not "
8203+
"match",
8204+
Explain(m, a));
8205+
8206+
m = Contains(GreaterThan(0)).Times(GreaterThan<size_t>(5));
8207+
EXPECT_EQ(
8208+
"whose elements (0, 1) match but whose match quantity of 2 does not "
8209+
"match, which is 3 less than 5",
8210+
Explain(m, a));
8211+
}
8212+
8213+
TEST(ContainsTimes, DescribesItselfCorrectly) {
8214+
Matcher<vector<int>> m = Contains(1).Times(2);
8215+
EXPECT_EQ("quantity of elements that match is equal to 1 is equal to 2",
8216+
Describe(m));
8217+
8218+
Matcher<vector<int>> m2 = Not(m);
8219+
EXPECT_EQ("quantity of elements that match is equal to 1 isn't equal to 2",
8220+
Describe(m2));
8221+
}
8222+
8223+
// Tests AllOfArray()
8224+
81508225
TEST(AllOfArrayTest, BasicForms) {
81518226
// Iterator
81528227
std::vector<int> v0{};

0 commit comments

Comments
 (0)