From 1cc1d4bcecdac906250f863bef2636b278eb679d Mon Sep 17 00:00:00 2001 From: "zhanyong.wan" Date: Thu, 8 Aug 2013 18:41:51 +0000 Subject: [PATCH] Makes some container matchers accept initializer lists in C++11 mode and work with stream-like containers that don't have size() or empty(); exposes StringMatchResultListener for defining composite matchers. --- CHANGES | 4 + include/gmock/gmock-matchers.h | 155 +++++++++++++++++--------- test/gmock-generated-matchers_test.cc | 39 +++++++ test/gmock-matchers_test.cc | 70 +++++++++++- 4 files changed, 214 insertions(+), 54 deletions(-) diff --git a/CHANGES b/CHANGES index 29efd728..e42e68fc 100644 --- a/CHANGES +++ b/CHANGES @@ -8,8 +8,12 @@ Changes for 1.7.0: * Improvement: Google Mock can now be built as a DLL. * Improvement: when compiled by a C++11 compiler, matchers AllOf() and AnyOf() can accept an arbitrary number of matchers. +* Improvement: when compiled by a C++11 compiler, matchers + ElementsAreArray() can accept an initializer list. * Improvement: when exceptions are enabled, a mock method with no default action now throws instead crashing the test. +* Improvement: added class testing::StringMatchResultListener to aid + definition of composite matchers. * Improvement: function return types used in MOCK_METHOD*() macros can now contain unprotected commas. * Improvement (potentially breaking): EXPECT_THAT() and ASSERT_THAT() diff --git a/include/gmock/gmock-matchers.h b/include/gmock/gmock-matchers.h index 0f52ee45..44055c93 100644 --- a/include/gmock/gmock-matchers.h +++ b/include/gmock/gmock-matchers.h @@ -52,6 +52,10 @@ #include "gmock/internal/gmock-port.h" #include "gtest/gtest.h" +#if GTEST_LANG_CXX11 +#include // NOLINT -- must be after gtest.h +#endif + namespace testing { // To implement a matcher Foo for type T, define: @@ -76,7 +80,8 @@ namespace testing { class MatchResultListener { public: // Creates a listener object with the given underlying ostream. The - // listener does not own the ostream. + // listener does not own the ostream, and does not dereference it + // in the constructor or destructor. explicit MatchResultListener(::std::ostream* os) : stream_(os) {} virtual ~MatchResultListener() = 0; // Makes this class abstract. @@ -175,6 +180,23 @@ class MatcherInterface : public MatcherDescriberInterface { // virtual void DescribeNegationTo(::std::ostream* os) const; }; +// A match result listener that stores the explanation in a string. +class StringMatchResultListener : public MatchResultListener { + public: + StringMatchResultListener() : MatchResultListener(&ss_) {} + + // Returns the explanation accumulated so far. + internal::string str() const { return ss_.str(); } + + // Clears the explanation accumulated so far. + void Clear() { ss_.str(""); } + + private: + ::std::stringstream ss_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); +}; + namespace internal { // A match result listener that ignores the explanation. @@ -198,20 +220,6 @@ class StreamMatchResultListener : public MatchResultListener { GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamMatchResultListener); }; -// A match result listener that stores the explanation in a string. -class StringMatchResultListener : public MatchResultListener { - public: - StringMatchResultListener() : MatchResultListener(&ss_) {} - - // Returns the explanation heard so far. - internal::string str() const { return ss_.str(); } - - private: - ::std::stringstream ss_; - - GTEST_DISALLOW_COPY_AND_ASSIGN_(StringMatchResultListener); -}; - // An internal class for implementing Matcher, which will derive // from it. We put functionalities common to all Matcher // specializations here to avoid code duplication. @@ -2911,49 +2919,81 @@ class ElementsAreMatcherImpl : public MatcherInterface { virtual bool MatchAndExplain(Container container, MatchResultListener* listener) const { + // To work with stream-like "containers", we must only walk + // through the elements in one pass. + + const bool listener_interested = listener->IsInterested(); + + // explanations[i] is the explanation of the element at index i. + ::std::vector explanations(count()); StlContainerReference stl_container = View::ConstReference(container); - const size_t actual_count = stl_container.size(); + typename StlContainer::const_iterator it = stl_container.begin(); + size_t exam_pos = 0; + bool mismatch_found = false; // Have we found a mismatched element yet? + + // Go through the elements and matchers in pairs, until we reach + // the end of either the elements or the matchers, or until we find a + // mismatch. + for (; it != stl_container.end() && exam_pos != count(); ++it, ++exam_pos) { + bool match; // Does the current element match the current matcher? + if (listener_interested) { + StringMatchResultListener s; + match = matchers_[exam_pos].MatchAndExplain(*it, &s); + explanations[exam_pos] = s.str(); + } else { + match = matchers_[exam_pos].Matches(*it); + } + + if (!match) { + mismatch_found = true; + break; + } + } + // If mismatch_found is true, 'exam_pos' is the index of the mismatch. + + // Find how many elements the actual container has. We avoid + // calling size() s.t. this code works for stream-like "containers" + // that don't define size(). + size_t actual_count = exam_pos; + for (; it != stl_container.end(); ++it) { + ++actual_count; + } + if (actual_count != count()) { // The element count doesn't match. If the container is empty, // there's no need to explain anything as Google Mock already // prints the empty container. Otherwise we just need to show // how many elements there actually are. - if (actual_count != 0 && listener->IsInterested()) { + if (listener_interested && (actual_count != 0)) { *listener << "which has " << Elements(actual_count); } return false; } - typename StlContainer::const_iterator it = stl_container.begin(); - // explanations[i] is the explanation of the element at index i. - ::std::vector explanations(count()); - for (size_t i = 0; i != count(); ++it, ++i) { - StringMatchResultListener s; - if (matchers_[i].MatchAndExplain(*it, &s)) { - explanations[i] = s.str(); - } else { - // The container has the right size but the i-th element - // doesn't match its expectation. - *listener << "whose element #" << i << " doesn't match"; - PrintIfNotEmpty(s.str(), listener->stream()); - return false; + if (mismatch_found) { + // The element count matches, but the exam_pos-th element doesn't match. + if (listener_interested) { + *listener << "whose element #" << exam_pos << " doesn't match"; + PrintIfNotEmpty(explanations[exam_pos], listener->stream()); } + return false; } // Every element matches its expectation. We need to explain why // (the obvious ones can be skipped). - bool reason_printed = false; - for (size_t i = 0; i != count(); ++i) { - const internal::string& s = explanations[i]; - if (!s.empty()) { - if (reason_printed) { - *listener << ",\nand "; + if (listener_interested) { + bool reason_printed = false; + for (size_t i = 0; i != count(); ++i) { + const internal::string& s = explanations[i]; + if (!s.empty()) { + if (reason_printed) { + *listener << ",\nand "; + } + *listener << "whose element #" << i << " matches, " << s; + reason_printed = true; } - *listener << "whose element #" << i << " matches, " << s; - reason_printed = true; } } - return true; } @@ -3273,21 +3313,14 @@ GTEST_API_ string FormatMatcherDescription(bool negation, // ElementsAreArray(pointer, count) // ElementsAreArray(array) // ElementsAreArray(vector) +// ElementsAreArray({ e1, e2, ..., en }) // -// The ElementsAreArray() functions are like ElementsAre(...), except that -// they are given a homogeneous sequence rather than taking each element as -// a function argument. The sequence can be specified as an array, a -// pointer and count, a vector, or an STL iterator range. In each of these -// cases, the underlying sequence can be either a sequence of values or a -// sequence of matchers. -// -// * ElementsAreArray(array) deduces the size of the array. -// -// * ElementsAreArray(pointer, count) form takes a pointer and count. -// -// * ElementsAreArray(vector) takes a std::vector. -// -// * ElementsAreArray(first, last) takes any iterator range. +// The ElementsAreArray() functions are like ElementsAre(...), except +// that they are given a homogeneous sequence rather than taking each +// element as a function argument. The sequence can be specified as an +// array, a pointer and count, a vector, an initializer list, or an +// STL iterator range. In each of these cases, the underlying sequence +// can be either a sequence of values or a sequence of matchers. // // All forms of ElementsAreArray() make a copy of the input matcher sequence. @@ -3317,10 +3350,19 @@ inline internal::ElementsAreArrayMatcher ElementsAreArray( return ElementsAreArray(vec.begin(), vec.end()); } +#if GTEST_LANG_CXX11 +template +inline internal::ElementsAreArrayMatcher +ElementsAreArray(::std::initializer_list xs) { + return ElementsAreArray(xs.begin(), xs.end()); +} +#endif + // UnorderedElementsAreArray(first, last) // UnorderedElementsAreArray(pointer, count) // UnorderedElementsAreArray(array) // UnorderedElementsAreArray(vector) +// UnorderedElementsAreArray({ e1, e2, ..., en }) // // The UnorderedElementsAreArray() functions are like // ElementsAreArray(...), but allow matching the elements in any order. @@ -3350,6 +3392,13 @@ UnorderedElementsAreArray(const ::std::vector& vec) { return UnorderedElementsAreArray(vec.begin(), vec.end()); } +#if GTEST_LANG_CXX11 +template +inline internal::UnorderedElementsAreArrayMatcher +UnorderedElementsAreArray(::std::initializer_list xs) { + return UnorderedElementsAreArray(xs.begin(), xs.end()); +} +#endif // _ is a matcher that matches anything of any type. // diff --git a/test/gmock-generated-matchers_test.cc b/test/gmock-generated-matchers_test.cc index e43781b6..dba74ecb 100644 --- a/test/gmock-generated-matchers_test.cc +++ b/test/gmock-generated-matchers_test.cc @@ -64,6 +64,7 @@ using testing::ElementsAreArray; using testing::Eq; using testing::Ge; using testing::Gt; +using testing::Le; using testing::Lt; using testing::MakeMatcher; using testing::Matcher; @@ -632,6 +633,44 @@ TEST(ElementsAreArrayTest, CanBeCreatedWithVector) { EXPECT_THAT(test_vector, Not(ElementsAreArray(expected))); } +#if GTEST_LANG_CXX11 + +TEST(ElementsAreArrayTest, TakesInitializerList) { + const int a[5] = { 1, 2, 3, 4, 5 }; + EXPECT_THAT(a, ElementsAreArray({ 1, 2, 3, 4, 5 })); + EXPECT_THAT(a, Not(ElementsAreArray({ 1, 2, 3, 5, 4 }))); + EXPECT_THAT(a, Not(ElementsAreArray({ 1, 2, 3, 4, 6 }))); +} + +TEST(ElementsAreArrayTest, TakesInitializerListOfCStrings) { + const string a[5] = { "a", "b", "c", "d", "e" }; + EXPECT_THAT(a, ElementsAreArray({ "a", "b", "c", "d", "e" })); + EXPECT_THAT(a, Not(ElementsAreArray({ "a", "b", "c", "e", "d" }))); + EXPECT_THAT(a, Not(ElementsAreArray({ "a", "b", "c", "d", "ef" }))); +} + +TEST(ElementsAreArrayTest, TakesInitializerListOfSameTypedMatchers) { + const int a[5] = { 1, 2, 3, 4, 5 }; + EXPECT_THAT(a, ElementsAreArray( + { Eq(1), Eq(2), Eq(3), Eq(4), Eq(5) })); + EXPECT_THAT(a, Not(ElementsAreArray( + { Eq(1), Eq(2), Eq(3), Eq(4), Eq(6) }))); +} + +TEST(ElementsAreArrayTest, + TakesInitializerListOfDifferentTypedMatchers) { + const int a[5] = { 1, 2, 3, 4, 5 }; + // The compiler cannot infer the type of the initializer list if its + // elements have different types. We must explicitly specify the + // unified element type in this case. + EXPECT_THAT(a, ElementsAreArray >( + { Eq(1), Ne(-2), Ge(3), Le(4), Eq(5) })); + EXPECT_THAT(a, Not(ElementsAreArray >( + { Eq(1), Ne(-2), Ge(3), Le(4), Eq(6) }))); +} + +#endif // GTEST_LANG_CXX11 + TEST(ElementsAreArrayTest, CanBeCreatedWithMatcherVector) { const int a[] = { 1, 2, 3 }; const Matcher kMatchers[] = { Eq(1), Eq(2), Eq(3) }; diff --git a/test/gmock-matchers_test.cc b/test/gmock-matchers_test.cc index ae97c9e0..4644f91f 100644 --- a/test/gmock-matchers_test.cc +++ b/test/gmock-matchers_test.cc @@ -124,6 +124,7 @@ using testing::Ref; using testing::ResultOf; using testing::SizeIs; using testing::StartsWith; +using testing::StringMatchResultListener; using testing::StrCaseEq; using testing::StrCaseNe; using testing::StrEq; @@ -145,7 +146,6 @@ using testing::internal::JoinAsTuple; using testing::internal::MatchMatrix; using testing::internal::RE; using testing::internal::StreamMatchResultListener; -using testing::internal::StringMatchResultListener; using testing::internal::Strings; using testing::internal::linked_ptr; using testing::internal::scoped_ptr; @@ -222,6 +222,12 @@ TEST(MatchResultListenerTest, StreamingWorks) { listener << "hi" << 5; EXPECT_EQ("hi5", listener.str()); + listener.Clear(); + EXPECT_EQ("", listener.str()); + + listener << 42; + EXPECT_EQ("42", listener.str()); + // Streaming shouldn't crash when the underlying ostream is NULL. DummyMatchResultListener dummy; dummy << "hi" << 5; @@ -4443,6 +4449,32 @@ TEST(WhenSortedTest, WorksForVectorConstRefMatcherOnStreamlike) { EXPECT_THAT(s, Not(WhenSorted(ElementsAre(2, 1, 4, 5, 3)))); } +// Tests using ElementsAre() and ElementsAreArray() with stream-like +// "containers". + +TEST(ElemensAreStreamTest, WorksForStreamlike) { + const int a[5] = { 1, 2, 3, 4, 5 }; + Streamlike s(a, a + GMOCK_ARRAY_SIZE_(a)); + EXPECT_THAT(s, ElementsAre(1, 2, 3, 4, 5)); + EXPECT_THAT(s, Not(ElementsAre(2, 1, 4, 5, 3))); +} + +TEST(ElemensAreArrayStreamTest, WorksForStreamlike) { + const int a[5] = { 1, 2, 3, 4, 5 }; + Streamlike s(a, a + GMOCK_ARRAY_SIZE_(a)); + + vector expected; + expected.push_back(1); + expected.push_back(2); + expected.push_back(3); + expected.push_back(4); + expected.push_back(5); + EXPECT_THAT(s, ElementsAreArray(expected)); + + expected[3] = 0; + EXPECT_THAT(s, Not(ElementsAreArray(expected))); +} + // Tests for UnorderedElementsAreArray() TEST(UnorderedElementsAreArrayTest, SucceedsWhenExpected) { @@ -4484,6 +4516,42 @@ TEST(UnorderedElementsAreArrayTest, WorksForStreamlike) { EXPECT_THAT(s, Not(UnorderedElementsAreArray(expected))); } +#if GTEST_LANG_CXX11 + +TEST(UnorderedElementsAreArrayTest, TakesInitializerList) { + const int a[5] = { 2, 1, 4, 5, 3 }; + EXPECT_THAT(a, UnorderedElementsAreArray({ 1, 2, 3, 4, 5 })); + EXPECT_THAT(a, Not(UnorderedElementsAreArray({ 1, 2, 3, 4, 6 }))); +} + +TEST(UnorderedElementsAreArrayTest, TakesInitializerListOfCStrings) { + const string a[5] = { "a", "b", "c", "d", "e" }; + EXPECT_THAT(a, UnorderedElementsAreArray({ "a", "b", "c", "d", "e" })); + EXPECT_THAT(a, Not(UnorderedElementsAreArray({ "a", "b", "c", "d", "ef" }))); +} + +TEST(UnorderedElementsAreArrayTest, TakesInitializerListOfSameTypedMatchers) { + const int a[5] = { 2, 1, 4, 5, 3 }; + EXPECT_THAT(a, UnorderedElementsAreArray( + { Eq(1), Eq(2), Eq(3), Eq(4), Eq(5) })); + EXPECT_THAT(a, Not(UnorderedElementsAreArray( + { Eq(1), Eq(2), Eq(3), Eq(4), Eq(6) }))); +} + +TEST(UnorderedElementsAreArrayTest, + TakesInitializerListOfDifferentTypedMatchers) { + const int a[5] = { 2, 1, 4, 5, 3 }; + // The compiler cannot infer the type of the initializer list if its + // elements have different types. We must explicitly specify the + // unified element type in this case. + EXPECT_THAT(a, UnorderedElementsAreArray >( + { Eq(1), Ne(-2), Ge(3), Le(4), Eq(5) })); + EXPECT_THAT(a, Not(UnorderedElementsAreArray >( + { Eq(1), Ne(-2), Ge(3), Le(4), Eq(6) }))); +} + +#endif // GTEST_LANG_CXX11 + class UnorderedElementsAreTest : public testing::Test { protected: typedef std::vector IntVec;