Googletest export

Implement 'Contains(e).Times(n)' matcher modifier which allows to test for arbitrary occurrences including absence with Times(0).

PiperOrigin-RevId: 382210276
This commit is contained in:
Abseil Team 2021-06-29 21:55:02 -04:00 committed by Andy Soffer
parent 5f97ce4c70
commit 4ec4cd23f4
3 changed files with 209 additions and 17 deletions

View File

@ -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. | | `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. | | `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)` | `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. | | `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. | | `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. | | `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. |

View File

@ -2581,7 +2581,7 @@ class PointwiseMatcher {
StringMatchResultListener inner_listener; StringMatchResultListener inner_listener;
// Create InnerMatcherArg as a temporarily object to avoid it outlives // Create InnerMatcherArg as a temporarily object to avoid it outlives
// *left and *right. Dereference or the conversion to `const T&` may // *left and *right. Dereference or the conversion to `const T&` may
// return temp objects, e.g for vector<bool>. // return temp objects, e.g. for vector<bool>.
if (!mono_tuple_matcher_.MatchAndExplain( if (!mono_tuple_matcher_.MatchAndExplain(
InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left), InnerMatcherArg(ImplicitCast_<const LhsValue&>(*left),
ImplicitCast_<const RhsValue&>(*right)), ImplicitCast_<const RhsValue&>(*right)),
@ -2653,6 +2653,54 @@ class QuantifierMatcherImpl : public MatcherInterface<Container> {
return all_elements_should_match; return all_elements_should_match;
} }
bool MatchAndExplainImpl(const Matcher<size_t>& count_matcher,
Container container,
MatchResultListener* listener) const {
StlContainerReference stl_container = View::ConstReference(container);
size_t i = 0;
std::vector<size_t> 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: protected:
const Matcher<const Element&> inner_matcher_; const Matcher<const Element&> inner_matcher_;
}; };
@ -2709,6 +2757,58 @@ class EachMatcherImpl : public QuantifierMatcherImpl<Container> {
} }
}; };
// Implements Contains(element_matcher).Times(n) for the given argument type
// Container.
template <typename Container>
class ContainsTimesMatcherImpl : public QuantifierMatcherImpl<Container> {
public:
template <typename InnerMatcher>
explicit ContainsTimesMatcherImpl(InnerMatcher inner_matcher,
Matcher<size_t> count_matcher)
: QuantifierMatcherImpl<Container>(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<size_t> count_matcher_;
};
// Implements polymorphic Contains(element_matcher).Times(n).
template <typename M>
class ContainsTimesMatcher {
public:
explicit ContainsTimesMatcher(M m, Matcher<size_t> count_matcher)
: inner_matcher_(m), count_matcher_(std::move(count_matcher)) {}
template <typename Container>
operator Matcher<Container>() const { // NOLINT
return Matcher<Container>(new ContainsTimesMatcherImpl<const Container&>(
inner_matcher_, count_matcher_));
}
private:
const M inner_matcher_;
const Matcher<size_t> count_matcher_;
};
// Implements polymorphic Contains(element_matcher). // Implements polymorphic Contains(element_matcher).
template <typename M> template <typename M>
class ContainsMatcher { class ContainsMatcher {
@ -2716,11 +2816,15 @@ class ContainsMatcher {
explicit ContainsMatcher(M m) : inner_matcher_(m) {} explicit ContainsMatcher(M m) : inner_matcher_(m) {}
template <typename Container> template <typename Container>
operator Matcher<Container>() const { operator Matcher<Container>() const { // NOLINT
return Matcher<Container>( return Matcher<Container>(
new ContainsMatcherImpl<const Container&>(inner_matcher_)); new ContainsMatcherImpl<const Container&>(inner_matcher_));
} }
ContainsTimesMatcher<M> Times(Matcher<size_t> count_matcher) const {
return ContainsTimesMatcher<M>(inner_matcher_, std::move(count_matcher));
}
private: private:
const M inner_matcher_; const M inner_matcher_;
}; };
@ -2732,7 +2836,7 @@ class EachMatcher {
explicit EachMatcher(M m) : inner_matcher_(m) {} explicit EachMatcher(M m) : inner_matcher_(m) {}
template <typename Container> template <typename Container>
operator Matcher<Container>() const { operator Matcher<Container>() const { // NOLINT
return Matcher<Container>( return Matcher<Container>(
new EachMatcherImpl<const Container&>(inner_matcher_)); new EachMatcherImpl<const Container&>(inner_matcher_));
} }
@ -4615,7 +4719,6 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
return UnorderedPointwise(tuple2_matcher, std::vector<T>(rhs)); return UnorderedPointwise(tuple2_matcher, std::vector<T>(rhs));
} }
// Matches an STL-style container or a native array that contains at // Matches an STL-style container or a native array that contains at
// least one element matching the given value or matcher. // least one element matching the given value or matcher.
// //
@ -4625,7 +4728,7 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
// page_ids.insert(1); // page_ids.insert(1);
// EXPECT_THAT(page_ids, Contains(1)); // EXPECT_THAT(page_ids, Contains(1));
// EXPECT_THAT(page_ids, Contains(Gt(2))); // 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<int, size_t> page_lengths; // ::std::map<int, size_t> page_lengths;
// page_lengths[1] = 100; // page_lengths[1] = 100;
@ -4634,6 +4737,19 @@ UnorderedPointwise(const Tuple2Matcher& tuple2_matcher,
// //
// const char* user_ids[] = { "joe", "mike", "tom" }; // const char* user_ids[] = { "joe", "mike", "tom" };
// EXPECT_THAT(user_ids, Contains(Eq(::std::string("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<int> 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 <typename M> template <typename M>
inline internal::ContainsMatcher<M> Contains(M matcher) { inline internal::ContainsMatcher<M> Contains(M matcher) {
return internal::ContainsMatcher<M>(matcher); return internal::ContainsMatcher<M>(matcher);
@ -4760,7 +4876,7 @@ inline internal::UnorderedElementsAreArrayMatcher<T> IsSubsetOf(
// Matches an STL-style container or a native array that contains only // Matches an STL-style container or a native array that contains only
// elements matching the given value or matcher. // 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. // the messages are different.
// //
// Examples: // Examples:

View File

@ -114,31 +114,32 @@ std::vector<std::unique_ptr<int>> MakeUniquePtrs(const std::vector<int>& ints) {
} }
// For testing ExplainMatchResultTo(). // For testing ExplainMatchResultTo().
class GreaterThanMatcher : public MatcherInterface<int> { template <typename T = int>
class GreaterThanMatcher : public MatcherInterface<T> {
public: public:
explicit GreaterThanMatcher(int rhs) : rhs_(rhs) {} explicit GreaterThanMatcher(T rhs) : rhs_(rhs) {}
void DescribeTo(ostream* os) const override { *os << "is > " << rhs_; } void DescribeTo(ostream* os) const override { *os << "is > " << rhs_; }
bool MatchAndExplain(int lhs, MatchResultListener* listener) const override { bool MatchAndExplain(T lhs, MatchResultListener* listener) const override {
const int diff = lhs - rhs_; if (lhs > rhs_) {
if (diff > 0) { *listener << "which is " << (lhs - rhs_) << " more than " << rhs_;
*listener << "which is " << diff << " more than " << rhs_; } else if (lhs == rhs_) {
} else if (diff == 0) {
*listener << "which is the same as " << rhs_; *listener << "which is the same as " << rhs_;
} else { } else {
*listener << "which is " << -diff << " less than " << rhs_; *listener << "which is " << (rhs_ - lhs) << " less than " << rhs_;
} }
return lhs > rhs_; return lhs > rhs_;
} }
private: private:
int rhs_; const T rhs_;
}; };
Matcher<int> GreaterThan(int n) { template <typename T>
return MakeMatcher(new GreaterThanMatcher(n)); Matcher<T> GreaterThan(T n) {
return MakeMatcher(new GreaterThanMatcher<T>(n));
} }
std::string OfType(const std::string& type_name) { std::string OfType(const std::string& type_name) {
@ -8023,6 +8024,7 @@ TEST(ContainsTest, ListMatchesWhenElementIsInContainer) {
some_list.push_back(3); some_list.push_back(3);
some_list.push_back(1); some_list.push_back(1);
some_list.push_back(2); some_list.push_back(2);
some_list.push_back(3);
EXPECT_THAT(some_list, Contains(1)); EXPECT_THAT(some_list, Contains(1));
EXPECT_THAT(some_list, Contains(Gt(2.5))); EXPECT_THAT(some_list, Contains(Gt(2.5)));
EXPECT_THAT(some_list, Contains(Eq(2.0f))); EXPECT_THAT(some_list, Contains(Eq(2.0f)));
@ -8147,6 +8149,79 @@ TEST(ContainsTest, WorksForTwoDimensionalNativeArray) {
EXPECT_THAT(a, Contains(Not(Contains(5)))); EXPECT_THAT(a, Contains(Not(Contains(5))));
} }
// Tests Contains().Times().
TEST(ContainsTimes, ListMatchesWhenElementQuantityMatches) {
list<int> 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<int>{}, Not(Contains(_)));
}
TEST(ContainsTimes, ExplainsMatchResultCorrectly) {
const int a[2] = {1, 2};
Matcher<const int(&)[2]> 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<size_t>(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<vector<int>> m = Contains(1).Times(2);
EXPECT_EQ("quantity of elements that match is equal to 1 is equal to 2",
Describe(m));
Matcher<vector<int>> 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) { TEST(AllOfArrayTest, BasicForms) {
// Iterator // Iterator
std::vector<int> v0{}; std::vector<int> v0{};