Skip to content

Commit b8269f3

Browse files
fshcheglovCommit Queue
authored andcommitted
Implement a better diff print on failing tests.
Use it for printing failing element model tests. Expectations in theses tests are often quite long, and it is hard to see what was changed in the original form of printing. The algorithm implemented is the Longest Common Sequence algorithm: https://en.wikipedia.org/wiki/Longest_common_subsequence BUG: #61015 Change-Id: I6f2af52b52a6b3365f36bb536172ad2d625ec93c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/439082 Reviewed-by: Paul Berry <[email protected]> Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent f36a637 commit b8269f3

File tree

2 files changed

+158
-3
lines changed

2 files changed

+158
-3
lines changed

pkg/analyzer/test/src/summary/elements_base.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:analyzer/src/dart/element/element.dart';
99
import 'package:test/test.dart';
1010
import 'package:test_reflective_loader/test_reflective_loader.dart';
1111

12+
import '../../util/diff.dart';
1213
import '../dart/resolution/context_collection_resolution.dart';
1314
import '../dart/resolution/node_text_expectations.dart';
1415
import 'element_text.dart';
@@ -49,11 +50,10 @@ abstract class ElementsBaseTest extends PubPackageResolutionTest {
4950
void checkElementText(LibraryElementImpl library, String expected) {
5051
var actual = getLibraryText(library: library, configuration: configuration);
5152
if (actual != expected) {
52-
print('-------- Actual --------');
53-
print('$actual------------------------');
5453
NodeTextExpectationsCollector.add(actual);
54+
printPrettyDiff(expected, actual);
55+
fail('See the difference above.');
5556
}
56-
expect(actual, expected);
5757
}
5858

5959
Future<LibraryElementImpl> testContextLibrary(String uriStr) async {

pkg/analyzer/test/util/diff.dart

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:math';
6+
7+
/// Generates a "focused" unified diff, showing only the changed lines
8+
/// plus a few lines of context around them.
9+
String generateFocusedDiff(
10+
String expected,
11+
String actual, {
12+
int contextLines = 3,
13+
}) {
14+
var fullDiff = _createDiff(expected, actual);
15+
var outputLines = <String>[];
16+
17+
var indicesToInclude = <int>{};
18+
19+
for (var (i, result) in fullDiff.indexed) {
20+
if (result.type != _DiffType.common) {
21+
indicesToInclude.add(i);
22+
for (
23+
int j = max(0, i - contextLines);
24+
j < min(fullDiff.length, i + contextLines + 1);
25+
j++
26+
) {
27+
indicesToInclude.add(j);
28+
}
29+
}
30+
}
31+
32+
if (indicesToInclude.isEmpty) {
33+
return 'No differences found.';
34+
}
35+
36+
int lastIncludedIndex = -1;
37+
var sortedIndices = indicesToInclude.toList()..sort();
38+
39+
for (int i in sortedIndices) {
40+
if (lastIncludedIndex != -1 && i > lastIncludedIndex + 1) {
41+
outputLines.add('...');
42+
}
43+
44+
outputLines.add(_formatLine(fullDiff[i]));
45+
lastIncludedIndex = i;
46+
}
47+
48+
return outputLines.join('\n');
49+
}
50+
51+
/// Generates a "full" unified diff, showing every line from the comparison.
52+
String generateFullDiff(String expected, String actual) {
53+
var diffResults = _createDiff(expected, actual);
54+
return diffResults.map(_formatLine).join('\n');
55+
}
56+
57+
void printPrettyDiff(String expected, String actual, {int context = 3}) {
58+
var full = generateFullDiff(expected, actual);
59+
var short = generateFocusedDiff(expected, actual);
60+
print('-------- Short diff --------');
61+
print(short);
62+
print('-------- Full diff ---------');
63+
print(full);
64+
print('---------- Actual ----------');
65+
print(actual.trimRight());
66+
print('------------------------');
67+
}
68+
69+
/// Backtracks through the LCS table to build the list of diff results.
70+
List<_DiffResult> _backtrack(
71+
List<List<int>> table,
72+
List<String> list1,
73+
List<String> list2,
74+
) {
75+
var diff = <_DiffResult>[];
76+
int i = list1.length;
77+
int j = list2.length;
78+
79+
while (i > 0 || j > 0) {
80+
if (i == 0) {
81+
diff.add(_DiffResult(_DiffType.added, list2[j - 1]));
82+
j--;
83+
continue;
84+
}
85+
if (j == 0) {
86+
diff.add(_DiffResult(_DiffType.removed, list1[i - 1]));
87+
i--;
88+
continue;
89+
}
90+
if (list1[i - 1] == list2[j - 1]) {
91+
diff.add(_DiffResult(_DiffType.common, list1[i - 1]));
92+
i--;
93+
j--;
94+
} else if (table[i - 1][j] >= table[i][j - 1]) {
95+
diff.add(_DiffResult(_DiffType.removed, list1[i - 1]));
96+
i--;
97+
} else {
98+
diff.add(_DiffResult(_DiffType.added, list2[j - 1]));
99+
j--;
100+
}
101+
}
102+
103+
return diff.reversed.toList();
104+
}
105+
106+
List<List<int>> _computeLcsTable(List<String> list1, List<String> list2) {
107+
var n = list1.length;
108+
var m = list2.length;
109+
110+
var table = List.generate(n + 1, (_) => List.filled(m + 1, 0));
111+
112+
for (int i = 1; i <= n; i++) {
113+
for (int j = 1; j <= m; j++) {
114+
if (list1[i - 1] == list2[j - 1]) {
115+
table[i][j] = table[i - 1][j - 1] + 1;
116+
} else {
117+
table[i][j] = max(table[i - 1][j], table[i][j - 1]);
118+
}
119+
}
120+
}
121+
return table;
122+
}
123+
124+
/// Returns a line by line difference between [actual] and [expected].
125+
///
126+
/// It uses Longest Common Sequence Algorithm to generate a table
127+
/// (https://en.wikipedia.org/wiki/Longest_common_subsequence)
128+
List<_DiffResult> _createDiff(String expected, String actual) {
129+
var expectedLines = expected.split('\n');
130+
var actualLines = actual.split('\n');
131+
132+
var lcsTable = _computeLcsTable(expectedLines, actualLines);
133+
134+
return _backtrack(lcsTable, expectedLines, actualLines);
135+
}
136+
137+
String _formatLine(_DiffResult result) {
138+
switch (result.type) {
139+
case _DiffType.added:
140+
return '+ ${result.line}';
141+
case _DiffType.removed:
142+
return '- ${result.line}';
143+
case _DiffType.common:
144+
return ' ${result.line}';
145+
}
146+
}
147+
148+
class _DiffResult {
149+
final _DiffType type;
150+
final String line;
151+
152+
_DiffResult(this.type, this.line);
153+
}
154+
155+
enum _DiffType { added, removed, common }

0 commit comments

Comments
 (0)