diff --git a/include/gmock/gmock-matchers.h b/include/gmock/gmock-matchers.h index 9445bf91..89a9e2ec 100644 --- a/include/gmock/gmock-matchers.h +++ b/include/gmock/gmock-matchers.h @@ -1970,6 +1970,85 @@ class ContainerEqMatcher { GTEST_DISALLOW_ASSIGN_(ContainerEqMatcher); }; +// A comparator functor that uses the < operator to compare two values. +struct LessComparator { + template + bool operator()(const T& lhs, const U& rhs) const { return lhs < rhs; } +}; + +// Implements WhenSortedBy(comparator, container_matcher). +template +class WhenSortedByMatcher { + public: + WhenSortedByMatcher(const Comparator& comparator, + const ContainerMatcher& matcher) + : comparator_(comparator), matcher_(matcher) {} + + template + operator Matcher() const { + return MakeMatcher(new Impl(comparator_, matcher_)); + } + + template + class Impl : public MatcherInterface { + public: + typedef internal::StlContainerView< + GTEST_REMOVE_REFERENCE_AND_CONST_(LhsContainer)> LhsView; + typedef typename LhsView::type LhsStlContainer; + typedef typename LhsView::const_reference LhsStlContainerReference; + typedef typename LhsStlContainer::value_type LhsValue; + + Impl(const Comparator& comparator, const ContainerMatcher& matcher) + : comparator_(comparator), matcher_(matcher) {} + + virtual void DescribeTo(::std::ostream* os) const { + *os << "(when sorted) "; + matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream* os) const { + *os << "(when sorted) "; + matcher_.DescribeNegationTo(os); + } + + virtual bool MatchAndExplain(LhsContainer lhs, + MatchResultListener* listener) const { + LhsStlContainerReference lhs_stl_container = LhsView::ConstReference(lhs); + std::vector sorted_container(lhs_stl_container.begin(), + lhs_stl_container.end()); + std::sort(sorted_container.begin(), sorted_container.end(), comparator_); + + if (!listener->IsInterested()) { + // If the listener is not interested, we do not need to + // construct the inner explanation. + return matcher_.Matches(sorted_container); + } + + *listener << "which is "; + UniversalPrint(sorted_container, listener->stream()); + *listener << " when sorted"; + + StringMatchResultListener inner_listener; + const bool match = matcher_.MatchAndExplain(sorted_container, + &inner_listener); + PrintIfNotEmpty(inner_listener.str(), listener->stream()); + return match; + } + + private: + const Comparator comparator_; + const Matcher&> matcher_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Impl); + }; + + private: + const Comparator comparator_; + const ContainerMatcher matcher_; + + GTEST_DISALLOW_ASSIGN_(WhenSortedByMatcher); +}; + // Implements Pointwise(tuple_matcher, rhs_container). tuple_matcher // must be able to be safely cast to Matcher >, where T1 and T2 are the types of elements in the LHS @@ -2930,6 +3009,26 @@ inline PolymorphicMatcher(rhs)); } +// Returns a matcher that matches a container that, when sorted using +// the given comparator, matches container_matcher. +template +inline internal::WhenSortedByMatcher +WhenSortedBy(const Comparator& comparator, + const ContainerMatcher& container_matcher) { + return internal::WhenSortedByMatcher( + comparator, container_matcher); +} + +// Returns a matcher that matches a container that, when sorted using +// the < operator, matches container_matcher. +template +inline internal::WhenSortedByMatcher +WhenSorted(const ContainerMatcher& container_matcher) { + return + internal::WhenSortedByMatcher( + internal::LessComparator(), container_matcher); +} + // Matches an STL-style container or a native array that contains the // same number of elements as in rhs, where its i-th element and rhs's // i-th element (as a pair) satisfy the given pair matcher, for all i. diff --git a/test/gmock-matchers_test.cc b/test/gmock-matchers_test.cc index 8f96efc2..c4ed96ba 100644 --- a/test/gmock-matchers_test.cc +++ b/test/gmock-matchers_test.cc @@ -57,6 +57,8 @@ GTEST_API_ string JoinAsTuple(const Strings& fields); namespace gmock_matchers_test { +using std::greater; +using std::less; using std::list; using std::make_pair; using std::map; @@ -118,6 +120,8 @@ using testing::StrNe; using testing::Truly; using testing::TypedEq; using testing::Value; +using testing::WhenSorted; +using testing::WhenSortedBy; using testing::_; using testing::internal::DummyMatchResultListener; using testing::internal::ExplainMatchFailureTupleTo; @@ -3725,6 +3729,83 @@ TEST(ContainerEqExtraTest, CopiesNativeArrayParameter) { EXPECT_THAT(a1, m); } +TEST(WhenSortedByTest, WorksForEmptyContainer) { + const vector numbers; + EXPECT_THAT(numbers, WhenSortedBy(less(), ElementsAre())); + EXPECT_THAT(numbers, Not(WhenSortedBy(less(), ElementsAre(1)))); +} + +TEST(WhenSortedByTest, WorksForNonEmptyContainer) { + vector numbers; + numbers.push_back(3); + numbers.push_back(1); + numbers.push_back(2); + numbers.push_back(2); + EXPECT_THAT(numbers, WhenSortedBy(greater(), + ElementsAre(3, 2, 2, 1))); + EXPECT_THAT(numbers, Not(WhenSortedBy(greater(), + ElementsAre(1, 2, 2, 3)))); +} + +TEST(WhenSortedByTest, WorksForNonVectorContainer) { + list words; + words.push_back("say"); + words.push_back("hello"); + words.push_back("world"); + EXPECT_THAT(words, WhenSortedBy(less(), + ElementsAre("hello", "say", "world"))); + EXPECT_THAT(words, Not(WhenSortedBy(less(), + ElementsAre("say", "hello", "world")))); +} + +TEST(WhenSortedByTest, WorksForNativeArray) { + const int numbers[] = { 1, 3, 2, 4 }; + const int sorted_numbers[] = { 1, 2, 3, 4 }; + EXPECT_THAT(numbers, WhenSortedBy(less(), ElementsAre(1, 2, 3, 4))); + EXPECT_THAT(numbers, WhenSortedBy(less(), + ElementsAreArray(sorted_numbers))); + EXPECT_THAT(numbers, Not(WhenSortedBy(less(), ElementsAre(1, 3, 2, 4)))); +} + +TEST(WhenSortedByTest, CanDescribeSelf) { + const Matcher > m = WhenSortedBy(less(), ElementsAre(1, 2)); + EXPECT_EQ("(when sorted) has 2 elements where\n" + "element #0 is equal to 1,\n" + "element #1 is equal to 2", + Describe(m)); + EXPECT_EQ("(when sorted) doesn't have 2 elements, or\n" + "element #0 isn't equal to 1, or\n" + "element #1 isn't equal to 2", + DescribeNegation(m)); +} + +TEST(WhenSortedByTest, ExplainsMatchResult) { + const int a[] = { 2, 1 }; + EXPECT_EQ("which is { 1, 2 } when sorted, whose element #0 doesn't match", + Explain(WhenSortedBy(less(), ElementsAre(2, 3)), a)); + EXPECT_EQ("which is { 1, 2 } when sorted", + Explain(WhenSortedBy(less(), ElementsAre(1, 2)), a)); +} + +// WhenSorted() is a simple wrapper on WhenSortedBy(). Hence we don't +// need to test it as exhaustively as we test the latter. + +TEST(WhenSortedTest, WorksForEmptyContainer) { + const vector numbers; + EXPECT_THAT(numbers, WhenSorted(ElementsAre())); + EXPECT_THAT(numbers, Not(WhenSorted(ElementsAre(1)))); +} + +TEST(WhenSortedTest, WorksForNonEmptyContainer) { + list words; + words.push_back("3"); + words.push_back("1"); + words.push_back("2"); + words.push_back("2"); + EXPECT_THAT(words, WhenSorted(ElementsAre("1", "2", "2", "3"))); + EXPECT_THAT(words, Not(WhenSorted(ElementsAre("3", "1", "2", "2")))); +} + // Tests IsReadableTypeName(). TEST(IsReadableTypeNameTest, ReturnsTrueForShortNames) {