From 157ee5a197ced9928a695dc09c7192aabe60c17c Mon Sep 17 00:00:00 2001 From: siddh Date: Sat, 9 May 2026 13:35:10 +0530 Subject: [PATCH 1/4] Improve ElementsAreArray mismatch explanations for readability Use structured Expected/Actual-oriented diagnostics for ElementsAre and UnorderedElementsAre failures, and update matcher container tests to assert the new clearer output format. --- googlemock/include/gmock/gmock-matchers.h | 13 +- googlemock/src/gmock-matchers.cc | 64 ++++++---- .../test/gmock-matchers-containers_test.cc | 116 ++++++++++-------- 3 files changed, 114 insertions(+), 79 deletions(-) diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index 2f49371a6..32b1a7ddc 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -3708,7 +3708,9 @@ class [[nodiscard]] ElementsAreMatcherImpl // prints the empty container. Otherwise we just need to show // how many elements there actually are. if (listener_interested && (actual_count != 0)) { - *listener << "which has " << Elements(actual_count); + *listener << "size mismatch:\n" + << " Expected size: " << count() << "\n" + << " Actual size: " << actual_count; } return false; } @@ -3731,8 +3733,11 @@ class [[nodiscard]] ElementsAreMatcherImpl // corresponding matcher description. Therefore we print the index, the // value of the mismatched element, and the corresponding matcher // description to ease debugging. - *listener << "whose element #" << exam_pos << " (" - << PrintToString(*unmatched_it) << ") "; + *listener << "element mismatch at index " << exam_pos << ":\n"; + *listener << " Actual: " << PrintToString(*unmatched_it) << "\n"; + *listener << " Expected: "; + matchers_[exam_pos].DescribeTo(listener->stream()); + *listener << "\n Detail: "; matchers_[exam_pos].DescribeNegationTo(listener->stream()); PrintIfNotEmpty(explanations[exam_pos], listener->stream()); } @@ -3749,7 +3754,7 @@ class [[nodiscard]] ElementsAreMatcherImpl if (reason_printed) { *listener << ",\nand "; } - *listener << "whose element #" << i << " matches, " << s; + *listener << "element #" << i << " matches: " << s; reason_printed = true; } } diff --git a/googlemock/src/gmock-matchers.cc b/googlemock/src/gmock-matchers.cc index 277add6b6..fd7d78b06 100644 --- a/googlemock/src/gmock-matchers.cc +++ b/googlemock/src/gmock-matchers.cc @@ -243,6 +243,18 @@ static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs, os << "\n}"; } +static void LogActualExpectedPairVec(const ElementMatcherPairs& pairs, + ::std::ostream* stream) { + typedef ElementMatcherPairs::const_iterator Iter; + ::std::ostream& os = *stream; + const char* sep = ""; + for (Iter it = pairs.begin(); it != pairs.end(); ++it) { + os << sep << " - Actual #" << it->first << " <-> Expected #" + << it->second; + sep = "\n"; + } +} + bool MatchMatrix::NextGraph() { for (size_t ilhs = 0; ilhs < LhsSize(); ++ilhs) { for (size_t irhs = 0; irhs < RhsSize(); ++irhs) { @@ -399,33 +411,43 @@ bool UnorderedElementsAreMatcherImplBase::VerifyMatchMatrix( } if (match_flags() & UnorderedMatcherRequire::Superset) { - const char* sep = - "where the following matchers don't match any elements:\n"; + const char* sep = ""; + bool header_printed = false; for (size_t mi = 0; mi < matcher_matched.size(); ++mi) { if (matcher_matched[mi]) continue; result = false; if (listener->IsInterested()) { - *listener << sep << "matcher #" << mi << ": "; + if (!header_printed) { + *listener << "UnorderedElementsAre mismatch:\n" + << "Expected entries with no matching actual value:\n"; + header_printed = true; + } + *listener << sep << " - Expected #" << mi << ": "; matcher_describers_[mi]->DescribeTo(listener->stream()); - sep = ",\n"; + sep = "\n"; } } } if (match_flags() & UnorderedMatcherRequire::Subset) { - const char* sep = - "where the following elements don't match any matchers:\n"; + const char* sep = ""; const char* outer_sep = ""; if (!result) { - outer_sep = "\nand "; + outer_sep = "\n"; } + bool header_printed = false; for (size_t ei = 0; ei < element_matched.size(); ++ei) { if (element_matched[ei]) continue; result = false; if (listener->IsInterested()) { - *listener << outer_sep << sep << "element #" << ei << ": " + if (!header_printed) { + *listener << outer_sep + << "Actual entries with no matching expected value:\n"; + header_printed = true; + } + *listener << sep << " - Actual #" << ei << ": " << element_printouts[ei]; - sep = ",\n"; + sep = "\n"; outer_sep = ""; } } @@ -441,22 +463,22 @@ bool UnorderedElementsAreMatcherImplBase::FindPairing( if ((match_flags() & UnorderedMatcherRequire::Superset) && max_flow < matrix.RhsSize()) { if (listener->IsInterested()) { - *listener << "where no permutation of the elements can satisfy all " - "matchers, and the closest match is " - << max_flow << " of " << matrix.RhsSize() - << " matchers with the pairings:\n"; - LogElementMatcherPairVec(matches, listener->stream()); + *listener << "UnorderedElementsAre pairing mismatch:\n" + << " Matched " << max_flow << " of " << matrix.RhsSize() + << " expected entries.\n" + << "Pairings (Actual <-> Expected):\n"; + LogActualExpectedPairVec(matches, listener->stream()); } return false; } if ((match_flags() & UnorderedMatcherRequire::Subset) && max_flow < matrix.LhsSize()) { if (listener->IsInterested()) { - *listener - << "where not all elements can be matched, and the closest match is " - << max_flow << " of " << matrix.RhsSize() - << " matchers with the pairings:\n"; - LogElementMatcherPairVec(matches, listener->stream()); + *listener << "UnorderedElementsAre pairing mismatch:\n" + << " Matched " << max_flow << " of " << matrix.LhsSize() + << " actual entries.\n" + << "Pairings (Actual <-> Expected):\n"; + LogActualExpectedPairVec(matches, listener->stream()); } return false; } @@ -465,8 +487,8 @@ bool UnorderedElementsAreMatcherImplBase::FindPairing( if (listener->IsInterested()) { const char* sep = "where:\n"; for (size_t mi = 0; mi < matches.size(); ++mi) { - *listener << sep << " - element #" << matches[mi].first - << " is matched by matcher #" << matches[mi].second; + *listener << sep << " - Actual #" << matches[mi].first + << " is matched by Expected #" << matches[mi].second; sep = ",\n"; } } diff --git a/googlemock/test/gmock-matchers-containers_test.cc b/googlemock/test/gmock-matchers-containers_test.cc index a5e1aebb1..8c1f4fa97 100644 --- a/googlemock/test/gmock-matchers-containers_test.cc +++ b/googlemock/test/gmock-matchers-containers_test.cc @@ -1274,7 +1274,10 @@ TEST(WhenSortedByTest, ExplainsMatchResult) { const int a[] = {2, 1}; EXPECT_EQ( Explain(WhenSortedBy(less(), ElementsAre(2, 3)), a), - "which is { 1, 2 } when sorted, whose element #0 (1) isn't equal to 2"); + "which is { 1, 2 } when sorted, element mismatch at index 0:\n" + " Actual: 1\n" + " Expected: is equal to 2\n" + " Detail: isn't equal to 2"); EXPECT_EQ(Explain(WhenSortedBy(less(), ElementsAre(1, 2)), a), "which is { 1, 2 } when sorted"); } @@ -1623,16 +1626,17 @@ TEST(IsSupersetOfTest, MatchAndExplain) { ASSERT_FALSE(ExplainMatchResult(IsSupersetOf(expected), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), - Eq("where the following matchers don't match any elements:\n" - "matcher #0: is equal to 1")); + Eq("UnorderedElementsAre mismatch:\n" + "Expected entries with no matching actual value:\n" + " - Expected #0: is equal to 1")); v.push_back(1); listener.Clear(); ASSERT_TRUE(ExplainMatchResult(IsSupersetOf(expected), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), Eq("where:\n" - " - element #0 is matched by matcher #1,\n" - " - element #2 is matched by matcher #0")); + " - Actual #0 is matched by Expected #1,\n" + " - Actual #2 is matched by Expected #0")); } TEST(IsSupersetOfTest, WorksForRhsInitializerList) { @@ -1751,16 +1755,16 @@ TEST(IsSubsetOfTest, MatchAndExplain) { ASSERT_FALSE(ExplainMatchResult(IsSubsetOf(expected), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), - Eq("where the following elements don't match any matchers:\n" - "element #1: 3")); + Eq("Actual entries with no matching expected value:\n" + " - Actual #1: 3")); expected.push_back(3); listener.Clear(); ASSERT_TRUE(ExplainMatchResult(IsSubsetOf(expected), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), Eq("where:\n" - " - element #0 is matched by matcher #1,\n" - " - element #1 is matched by matcher #2")); + " - Actual #0 is matched by Expected #1,\n" + " - Actual #1 is matched by Expected #2")); } TEST(IsSubsetOfTest, WorksForRhsInitializerList) { @@ -2314,12 +2318,13 @@ TEST_F(UnorderedElementsAreTest, FailMessageCountWrong) { << listener.str(); EXPECT_THAT(listener.str(), Eq("which has 1 element\n" - "where the following matchers don't match any elements:\n" - "matcher #0: is equal to 1,\n" - "matcher #1: is equal to 2,\n" - "matcher #2: is equal to 3\n" - "and where the following elements don't match any matchers:\n" - "element #0: 4")); + "UnorderedElementsAre mismatch:\n" + "Expected entries with no matching actual value:\n" + " - Expected #0: is equal to 1\n" + " - Expected #1: is equal to 2\n" + " - Expected #2: is equal to 3\n" + "Actual entries with no matching expected value:\n" + " - Actual #0: 4")); } TEST_F(UnorderedElementsAreTest, FailMessageCountWrongZero) { @@ -2328,10 +2333,11 @@ TEST_F(UnorderedElementsAreTest, FailMessageCountWrongZero) { EXPECT_FALSE(ExplainMatchResult(UnorderedElementsAre(1, 2, 3), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), - Eq("where the following matchers don't match any elements:\n" - "matcher #0: is equal to 1,\n" - "matcher #1: is equal to 2,\n" - "matcher #2: is equal to 3")); + Eq("UnorderedElementsAre mismatch:\n" + "Expected entries with no matching actual value:\n" + " - Expected #0: is equal to 1\n" + " - Expected #1: is equal to 2\n" + " - Expected #2: is equal to 3")); } TEST_F(UnorderedElementsAreTest, FailMessageUnmatchedMatchers) { @@ -2342,8 +2348,9 @@ TEST_F(UnorderedElementsAreTest, FailMessageUnmatchedMatchers) { EXPECT_FALSE(ExplainMatchResult(UnorderedElementsAre(1, 2), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), - Eq("where the following matchers don't match any elements:\n" - "matcher #1: is equal to 2")); + Eq("UnorderedElementsAre mismatch:\n" + "Expected entries with no matching actual value:\n" + " - Expected #1: is equal to 2")); } TEST_F(UnorderedElementsAreTest, FailMessageUnmatchedElements) { @@ -2353,9 +2360,9 @@ TEST_F(UnorderedElementsAreTest, FailMessageUnmatchedElements) { StringMatchResultListener listener; EXPECT_FALSE(ExplainMatchResult(UnorderedElementsAre(1, 1), v, &listener)) << listener.str(); - EXPECT_THAT(listener.str(), - Eq("where the following elements don't match any matchers:\n" - "element #1: 2")); + EXPECT_THAT(listener.str(), Eq("Actual entries with no matching expected " + "value:\n" + " - Actual #1: 2")); } TEST_F(UnorderedElementsAreTest, FailMessageUnmatchedMatcherAndElement) { @@ -2366,20 +2373,11 @@ TEST_F(UnorderedElementsAreTest, FailMessageUnmatchedMatcherAndElement) { EXPECT_FALSE(ExplainMatchResult(UnorderedElementsAre(1, 2), v, &listener)) << listener.str(); EXPECT_THAT(listener.str(), - Eq("where" - " the following matchers don't match any elements:\n" - "matcher #0: is equal to 1\n" - "and" - " where" - " the following elements don't match any matchers:\n" - "element #1: 3")); -} - -// Test helper for formatting element, matcher index pairs in expectations. -static std::string EMString(int element, int matcher) { - stringstream ss; - ss << "(element #" << element << ", matcher #" << matcher << ")"; - return ss.str(); + Eq("UnorderedElementsAre mismatch:\n" + "Expected entries with no matching actual value:\n" + " - Expected #0: is equal to 1\n" + "Actual entries with no matching expected value:\n" + " - Actual #1: 3")); } TEST_F(UnorderedElementsAreTest, FailMessageImperfectMatchOnly) { @@ -2394,20 +2392,22 @@ TEST_F(UnorderedElementsAreTest, FailMessageImperfectMatchOnly) { UnorderedElementsAre("a", "a", AnyOf("b", "c")), v, &listener)) << listener.str(); - std::string prefix = - "where no permutation of the elements can satisfy all matchers, " - "and the closest match is 2 of 3 matchers with the " - "pairings:\n"; + std::string prefix = "UnorderedElementsAre pairing mismatch:\n" + " Matched 2 of 3 expected entries.\n" + "Pairings (Actual <-> Expected):\n"; // We have to be a bit loose here, because there are 4 valid max matches. EXPECT_THAT( listener.str(), AnyOf( - prefix + "{\n " + EMString(0, 0) + ",\n " + EMString(1, 2) + "\n}", - prefix + "{\n " + EMString(0, 1) + ",\n " + EMString(1, 2) + "\n}", - prefix + "{\n " + EMString(0, 0) + ",\n " + EMString(2, 2) + "\n}", - prefix + "{\n " + EMString(0, 1) + ",\n " + EMString(2, 2) + - "\n}")); + prefix + " - Actual #0 <-> Expected #0\n" + " - Actual #1 <-> Expected #2", + prefix + " - Actual #0 <-> Expected #1\n" + " - Actual #1 <-> Expected #2", + prefix + " - Actual #0 <-> Expected #0\n" + " - Actual #2 <-> Expected #2", + prefix + " - Actual #0 <-> Expected #1\n" + " - Actual #2 <-> Expected #2")); } TEST_F(UnorderedElementsAreTest, Describe) { @@ -2759,8 +2759,8 @@ TEST(UnorderedPointwiseTest, RejectsWrongContent) { const int rhs[3] = {2, 6, 6}; EXPECT_THAT(lhs, Not(UnorderedPointwise(IsHalfOf(), rhs))); EXPECT_EQ( - "where the following elements don't match any matchers:\n" - "element #1: 2", + "Actual entries with no matching expected value:\n" + " - Actual #1: 2", Explain(UnorderedPointwise(IsHalfOf(), rhs), lhs)); } @@ -2938,8 +2938,8 @@ TEST_P(ElementsAreTestP, ExplainsNonTrivialMatch) { const int a[] = {10, 0, 100}; vector test_vector(std::begin(a), std::end(a)); EXPECT_EQ( - "whose element #0 matches, which is 9 more than 1,\n" - "and whose element #2 matches, which is 98 more than 2", + "element #0 matches: which is 9 more than 1,\n" + "and element #2 matches: which is 98 more than 2", Explain(m, test_vector)); } @@ -2951,7 +2951,8 @@ TEST(ElementsAreTest, CanExplainMismatchWrongSize) { EXPECT_EQ("", Explain(m, test_list)); test_list.push_back(1); - EXPECT_EQ("which has 1 element", Explain(m, test_list)); + EXPECT_EQ("size mismatch:\n Expected size: 2\n Actual size: 1", + Explain(m, test_list)); } TEST_P(ElementsAreTestP, CanExplainMismatchRightSize) { @@ -2960,11 +2961,18 @@ TEST_P(ElementsAreTestP, CanExplainMismatchRightSize) { vector v; v.push_back(2); v.push_back(1); - EXPECT_EQ(Explain(m, v), "whose element #0 (2) isn't equal to 1"); + EXPECT_EQ(Explain(m, v), + "element mismatch at index 0:\n" + " Actual: 2\n" + " Expected: is equal to 1\n" + " Detail: isn't equal to 1"); v[0] = 1; EXPECT_EQ(Explain(m, v), - "whose element #1 (1) is <= 5, which is 4 less than 5"); + "element mismatch at index 1:\n" + " Actual: 1\n" + " Expected: is > 5\n" + " Detail: is <= 5, which is 4 less than 5"); } TEST(ElementsAreTest, MatchesOneElementVector) { From 52138d0373d3b8c298f12bbefdbe2a466b7a0c95 Mon Sep 17 00:00:00 2001 From: siddh Date: Sun, 10 May 2026 23:06:34 +0530 Subject: [PATCH 2/4] Add clarifying comment for pairing order Add a short comment documenting why unordered pairing diagnostics print in Actual/Expected order. --- googlemock/src/gmock-matchers.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/googlemock/src/gmock-matchers.cc b/googlemock/src/gmock-matchers.cc index fd7d78b06..a1784fa91 100644 --- a/googlemock/src/gmock-matchers.cc +++ b/googlemock/src/gmock-matchers.cc @@ -245,6 +245,7 @@ static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs, static void LogActualExpectedPairVec(const ElementMatcherPairs& pairs, ::std::ostream* stream) { + // Keep pairings in Actual/Expected order for quick visual diffing. typedef ElementMatcherPairs::const_iterator Iter; ::std::ostream& os = *stream; const char* sep = ""; From b992f283ea111443da87b3d7b59c928962cef5d3 Mon Sep 17 00:00:00 2001 From: siddh Date: Sun, 10 May 2026 23:24:14 +0530 Subject: [PATCH 3/4] Remove temporary clarifying comment Drop the follow-up comment added for CLA retrigger so the PR includes only necessary matcher output changes. --- googlemock/src/gmock-matchers.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/googlemock/src/gmock-matchers.cc b/googlemock/src/gmock-matchers.cc index a1784fa91..fd7d78b06 100644 --- a/googlemock/src/gmock-matchers.cc +++ b/googlemock/src/gmock-matchers.cc @@ -245,7 +245,6 @@ static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs, static void LogActualExpectedPairVec(const ElementMatcherPairs& pairs, ::std::ostream* stream) { - // Keep pairings in Actual/Expected order for quick visual diffing. typedef ElementMatcherPairs::const_iterator Iter; ::std::ostream& os = *stream; const char* sep = ""; From 18258591c9fb3b395c211159baefb6a78017e2a9 Mon Sep 17 00:00:00 2001 From: Shruti Pundir <111945742+ShrutiPundir17@users.noreply.github.com> Date: Mon, 11 May 2026 23:17:40 +0530 Subject: [PATCH 4/4] Update gmock-matchers.cc --- googlemock/src/gmock-matchers.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/googlemock/src/gmock-matchers.cc b/googlemock/src/gmock-matchers.cc index fd7d78b06..a1784fa91 100644 --- a/googlemock/src/gmock-matchers.cc +++ b/googlemock/src/gmock-matchers.cc @@ -245,6 +245,7 @@ static void LogElementMatcherPairVec(const ElementMatcherPairs& pairs, static void LogActualExpectedPairVec(const ElementMatcherPairs& pairs, ::std::ostream* stream) { + // Keep pairings in Actual/Expected order for quick visual diffing. typedef ElementMatcherPairs::const_iterator Iter; ::std::ostream& os = *stream; const char* sep = "";