Files
carbon-lang/testing/base/unified_diff_test.cpp
T
Richard Smith f776cf0744 Improve readability of test failures. (#7609)
Reduce use of gmock matcher infrastructure for diagnosing mismatches,
and instead manually stream an explanation of the difference. The
gmock-style "EXPECT_THAT" approach adds an unsuppressable "Actual: ..."
line into the output that only contains unreadable and redundant noise.
We're getting zero value from using a matcher diagnostic here, so don't.

Before:
```
Value of: SplitOutput(test_file.actual_stdout)
Expected: matches elements with unified diff
  Actual: { "--- else.carbon", "", "constants {", "  %F.type: type = fn_type @F [concrete]", "  %empty_tuple.type: type = tuple_type () [concrete]", "  %F: %F.type = struct_value () [concrete]", "  %H.type: type = fn_type @H [concrete]", "  %H: %H.type = struct_value () [concrete]", "  %pattern_type: type = pattern_type bool [concrete]", "  %b.param_patt: %pattern_type = value_param_pattern [concrete]", "  %b.patt: %pattern_type = at_binding_pattern b, %b.param_patt [concrete]", "  %If.type: type = fn_type @If [concrete]", "  %If: %If.type = struct_value () [concrete]", "}", "", "file {", "  %If.decl: %If.type = fn_decl @If [concrete = constants.%If] {", "    %b.param_patt: %pattern_type = value_param_pattern [concrete = constants.%b.param_patt]", "    %b.patt: %pattern_type = at_binding_pattern b, %b.param_patt [concrete = constants.%b.patt]", "  } {", "    %b.param: bool = value_param call_param0", "    %.loc8: type = type_literal bool [concrete = bool]", "    %b: bool = wrapper_binding b, %b.param", "  }", "}", "", "fn @If(%b.param: bool) {", "!entry:", "  %b.ref: bool = name_ref b, %b", "  if %b.ref br !if.then else br !if.else", "", "!if.then:", ... }, unified diff (- expected, + actual):
=== diff in expected elements 4 to 11 (1-based index):
    %F.type: type = fn_type @F [concrete]
    %empty_tuple.type: type = tuple_type () [concrete]
    %F: %F.type = struct_value () [concrete]
- is equal to "  %G.type: type = fn_type @G [concrete]"
- is equal to "  %G: %G.type = struct_value () [concrete]"
    %H.type: type = fn_type @H [concrete]
    %H: %H.type = struct_value () [concrete]
    %pattern_type: type = pattern_type bool [concrete]
=== diff in expected elements 37 to 44 (1-based index):
    br !if.done
  
  !if.else:
- is equal to "  %G.ref: %G.type = name_ref G, file.%G.decl [concrete = constants.%G]"
- is equal to "  %G.call: init %empty_tuple.type = call %G.ref()"
    br !if.done
  
  !if.done:
=== diff end
```

After:
```
Value of: testing::Value(SplitOutput(test_file.actual_stdout), testing::ElementsAreArray(test_file.expected_stdout))
  Actual: false
Expected: true
unified diff (- expected, + actual):
=== diff in expected elements 4 to 11 (1-based index):
   %F.type: type = fn_type @F [concrete]
   %empty_tuple.type: type = tuple_type () [concrete]
   %F: %F.type = struct_value () [concrete]
-  %G.type: type = fn_type @G [concrete]
-  %G: %G.type = struct_value () [concrete]
   %H.type: type = fn_type @H [concrete]
   %H: %H.type = struct_value () [concrete]
   %pattern_type: type = pattern_type bool [concrete]
=== diff in expected elements 37 to 44 (1-based index):
   br !if.done
 
 !if.else:
-  %G.ref: %G.type = name_ref G, file.%G.decl [concrete = constants.%G]
-  %G.call: init %empty_tuple.type = call %G.ref()
   br !if.done
 
 !if.done:
=== diff end
``` 

Assisted-by: Gemini via Antigravity
2026-08-04 21:23:36 +00:00

288 lines
7.3 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "testing/base/unified_diff.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <sstream>
#include <string>
#include "llvm/ADT/SmallVector.h"
namespace Carbon::Testing {
namespace {
using ::testing::ElementsAreArray;
using ::testing::Matcher;
using ::testing::MatchesRegex;
using ::testing::StrEq;
// Asserts that when expected does not match actual, the string
// representation of the produced diff equals expected_diff.
auto ExpectUnifiedDiff(const llvm::SmallVector<std::string>& actual,
const llvm::SmallVector<Matcher<std::string>>& expected,
const std::string& expected_diff) -> void {
std::stringstream ss;
ss << UnifiedDiff(expected, actual);
EXPECT_THAT(ss.str(), testing::Eq(expected_diff));
}
TEST(UnifiedDiffTest, Matches) {
llvm::SmallVector<std::string> actual = {"A", "B", "C"};
llvm::SmallVector<Matcher<std::string>> expected = {StrEq("A"), StrEq("B"),
StrEq("C")};
EXPECT_TRUE(testing::Value(actual, ElementsAreArray(expected)))
<< UnifiedDiff(expected, actual);
std::stringstream ss;
ss << UnifiedDiff(expected, actual);
EXPECT_THAT(ss.str(), testing::Eq(""));
}
TEST(UnifiedDiffTest, MismatchMissing) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 3 (1-based index):
A
-B
C
=== diff end
)";
ExpectUnifiedDiff({"A", "C"}, {StrEq("A"), StrEq("B"), StrEq("C")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchExtra) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 2 (1-based index):
A
+B
C
=== diff end
)";
ExpectUnifiedDiff({"A", "B", "C"}, {StrEq("A"), StrEq("C")}, ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchBoth) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 2 (1-based index):
A
-C
+B
=== diff end
)";
ExpectUnifiedDiff({"A", "B"}, {StrEq("A"), StrEq("C")}, ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchMultiple) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 5 (1-based index):
A
-B
+X
C
-D
+Y
E
=== diff end
)";
ExpectUnifiedDiff(
{"A", "X", "C", "Y", "E"},
{StrEq("A"), StrEq("B"), StrEq("C"), StrEq("D"), StrEq("E")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchLongContext) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 2 to 8 (1-based index):
1
2
3
-X
+4
5
6
7
=== diff end
)";
ExpectUnifiedDiff({"0", "1", "2", "3", "4", "5", "6", "7", "8"},
{StrEq("0"), StrEq("1"), StrEq("2"), StrEq("3"), StrEq("X"),
StrEq("5"), StrEq("6"), StrEq("7"), StrEq("8")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, Mismatch5LineContext) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 7 (1-based index):
-X
+0
1
2
3
4
5
-Y
+6
=== diff end
)";
ExpectUnifiedDiff({"0", "1", "2", "3", "4", "5", "6"},
{StrEq("X"), StrEq("1"), StrEq("2"), StrEq("3"), StrEq("4"),
StrEq("5"), StrEq("Y")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, Mismatch6LineContext) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 8 (1-based index):
-X
+0
1
2
3
4
5
6
-Y
+7
=== diff end
)";
ExpectUnifiedDiff({"0", "1", "2", "3", "4", "5", "6", "7"},
{StrEq("X"), StrEq("1"), StrEq("2"), StrEq("3"), StrEq("4"),
StrEq("5"), StrEq("6"), StrEq("Y")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, Mismatch7LineContext) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 4 (1-based index):
-X
+0
1
2
3
=== diff in expected elements 6 to 9 (1-based index):
5
6
7
-Y
+8
=== diff end
)";
ExpectUnifiedDiff({"0", "1", "2", "3", "4", "5", "6", "7", "8"},
{StrEq("X"), StrEq("1"), StrEq("2"), StrEq("3"), StrEq("4"),
StrEq("5"), StrEq("6"), StrEq("7"), StrEq("Y")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchEmptyExpected) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 1 (1-based index):
+A
=== diff end
)";
ExpectUnifiedDiff({"A"}, {}, ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchEmptyActual) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 1 (1-based index):
-A
=== diff end
)";
ExpectUnifiedDiff({}, {StrEq("A")}, ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchLongDifference) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 4 (1-based index):
1
-2
-3
+X
+Y
+Z
4
=== diff end
)";
ExpectUnifiedDiff({"1", "X", "Y", "Z", "4"},
{StrEq("1"), StrEq("2"), StrEq("3"), StrEq("4")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchGreedyResyncActualMissing) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 6 (1-based index):
1
2
-3
+X
+7
4
5
6
=== diff end
)";
ExpectUnifiedDiff({"1", "2", "X", "7", "4", "5", "6", "7", "8", "9"},
{StrEq("1"), StrEq("2"), StrEq("3"), StrEq("4"), StrEq("5"),
StrEq("6"), StrEq("7"), StrEq("8"), StrEq("9")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchGreedyResyncExpectedMissing) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 7 (1-based index):
1
2
-X
-7
+3
4
5
6
=== diff end
)";
ExpectUnifiedDiff(
{"1", "2", "3", "4", "5", "6", "7", "8", "9"},
{StrEq("1"), StrEq("2"), StrEq("X"), StrEq("7"), StrEq("4"), StrEq("5"),
StrEq("6"), StrEq("7"), StrEq("8"), StrEq("9")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, MismatchRegexMatcher) {
constexpr char ExpectedDiff[] = R"(unified diff (- expected, + actual):
=== diff in expected elements 1 to 3 (1-based index):
A
-matches regular expression ".*B.*"
C
=== diff end
)";
ExpectUnifiedDiff({"A", "C"}, {StrEq("A"), MatchesRegex(".*B.*"), StrEq("C")},
ExpectedDiff);
}
TEST(UnifiedDiffTest, CheckSubset) {
constexpr char ExpectedDiff[] =
R"(unified diff (- expected, + actual) [+ lines are normal]:
=== diff in expected elements 1 to 4 (1-based index):
-X
+0
1
2
3
=== diff end
)";
std::stringstream ss;
llvm::SmallVector<Matcher<std::string>> expected = {
StrEq("X"), StrEq("1"), StrEq("2"), StrEq("3"), StrEq("4"),
StrEq("5"), StrEq("6"), StrEq("7"), StrEq("8"), StrEq("9")};
llvm::SmallVector<std::string> actual = {"0", "1", "2", "3", "4", "5", "6",
"7", "8", "9", "10", "11", "12"};
ss << UnifiedDiff(expected, actual, /*check_subset=*/true);
EXPECT_THAT(ss.str(), testing::Eq(ExpectedDiff));
}
} // namespace
} // namespace Carbon::Testing