Skip to content

Commit d9a26da

Browse files
committed
Include rich diff in tree output
1 parent 83777e6 commit d9a26da

File tree

5 files changed

+119
-34
lines changed

5 files changed

+119
-34
lines changed

junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,10 @@ public void buildAndThrow() throws AssertionFailedError {
192192
*/
193193
public AssertionFailedError build() {
194194
String reason = nullSafeGet(this.reason);
195+
String message = nullSafeGet(this.message);
195196
var assertionFailedError = new AssertionFailedError( //
196-
formatExceptionMessage(reason), //
197-
formatReason(reason), //
197+
formatExceptionMessage(reason, message), //
198+
formatReason(reason, message), //
198199
expected, //
199200
actual, //
200201
cause //
@@ -203,18 +204,21 @@ public AssertionFailedError build() {
203204
return assertionFailedError;
204205
}
205206

206-
private @Nullable String formatReason(@Nullable String reason) {
207+
private @Nullable String formatReason(@Nullable String reason, @Nullable String message) {
207208
if (mismatch) {
208-
return (reason == null ? "" : reason + ", ") + "expected did not match actual";
209+
// TODO: The suffix is implicit due to mismatch, but how to make explicit?
210+
reason = (reason == null ? "" : reason + ", ") + "expected did not match actual";
211+
}
212+
if (reason != null) {
213+
message = buildPrefix(message) + reason;
209214
}
210-
return reason;
215+
return message;
211216
}
212217

213-
private @Nullable String formatExceptionMessage(@Nullable String reason) {
218+
private @Nullable String formatExceptionMessage(@Nullable String reason, @Nullable String message) {
214219
if (mismatch && includeValuesInMessage) {
215220
reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual);
216221
}
217-
String message = nullSafeGet(this.message);
218222
if (reason != null) {
219223
message = buildPrefix(message) + reason;
220224
}

junit-platform-console/junit-platform-console.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies {
1818
compileOnlyApi(libs.jspecify)
1919

2020
shadowed(libs.picocli)
21-
// TODO: Sort out
21+
// TODO: Sort out relocation, licence, ect
2222
shadowed(libs.javadiffutils)
2323

2424
osgiVerification(projects.junitJupiterEngine)
@@ -31,7 +31,8 @@ tasks {
3131
options.compilerArgs.addAll(listOf(
3232
"-Xlint:-module", // due to qualified exports
3333
"--add-modules", "info.picocli",
34-
"--add-reads", "${javaModuleName}=info.picocli"
34+
"--add-reads", "${javaModuleName}=info.picocli",
35+
"-Xlint:-requires-automatic", // Java diff utils
3536
))
3637
options.errorprone.nullaway {
3738
excludedFieldAnnotations.addAll(

junit-platform-console/src/main/java/org/junit/platform/console/output/RichDiffFormatter.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,16 @@
1010

1111
package org.junit.platform.console.output;
1212

13+
import java.util.function.Function;
14+
1315
import com.github.difflib.text.DiffRowGenerator;
1416

15-
import org.junit.platform.commons.util.ExceptionUtils;
1617
import org.opentest4j.AssertionFailedError;
1718

1819
final class RichDiffFormatter {
1920
public String format(AssertionFailedError assertionFailed) {
20-
if (!(assertionFailed.isActualDefined() && assertionFailed.isExpectedDefined())) {
21-
return ExceptionUtils.readStackTrace(assertionFailed);
22-
}
23-
2421
StringBuilder builder = new StringBuilder();
2522

26-
builder.append(assertionFailed.getClass().getSimpleName());
2723
if (assertionFailed.isReasonDefined()) {
2824
builder.append(": ");
2925
builder.append(assertionFailed.getReason());
@@ -33,12 +29,43 @@ public String format(AssertionFailedError assertionFailed) {
3329
builder.append("+ actual - expected");
3430
builder.append(System.lineSeparator());
3531

36-
var generator = DiffRowGenerator.create().mergeOriginalRevised(true).build();
32+
var generator = DiffRowGenerator.create() //
33+
.lineNormalizer(Function.identity()) // Don't normalize lines
34+
.showInlineDiffs(false) //
35+
.build();
3736

38-
// TODO: But how to render the stacktrace?
37+
var diffRows = generator.generateDiffRows( //
38+
assertionFailed.getExpected().getStringRepresentation().lines().toList(), //
39+
assertionFailed.getActual().getStringRepresentation().lines().toList() //
40+
);
3941

40-
builder.append(generator.generateDiffRows(assertionFailed.getExpected().toString().lines().toList(),
41-
assertionFailed.getActual().toString().lines().toList()));
42+
diffRows.forEach(diffRow -> {
43+
switch (diffRow.getTag()) {
44+
case INSERT -> {
45+
builder.append("+ ");
46+
builder.append(diffRow.getNewLine());
47+
builder.append(System.lineSeparator());
48+
}
49+
case DELETE -> {
50+
builder.append("- ");
51+
builder.append(diffRow.getOldLine());
52+
builder.append(System.lineSeparator());
53+
}
54+
case CHANGE -> {
55+
builder.append("+ ");
56+
builder.append(diffRow.getOldLine());
57+
builder.append(System.lineSeparator());
58+
builder.append("- ");
59+
builder.append(diffRow.getNewLine());
60+
builder.append(System.lineSeparator());
61+
}
62+
case EQUAL -> {
63+
builder.append(" ");
64+
builder.append(diffRow.getNewLine());
65+
builder.append(System.lineSeparator());
66+
}
67+
}
68+
});
4269

4370
return builder.toString();
4471
}

junit-platform-console/src/main/java/org/junit/platform/console/output/TreePrinter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.junit.platform.engine.TestExecutionResult.Status;
2323
import org.junit.platform.engine.reporting.FileEntry;
2424
import org.junit.platform.engine.reporting.ReportEntry;
25+
import org.opentest4j.AssertionFailedError;
2526

2627
/**
2728
* @since 1.0
@@ -123,11 +124,19 @@ private void printThrowable(String indent, TestExecutionResult result) {
123124
return;
124125
}
125126
Throwable throwable = result.getThrowable().get();
127+
String message = formatThrowable(throwable);
128+
printMessage(Style.FAILED, indent, message);
129+
}
130+
131+
private static String formatThrowable(Throwable throwable) {
132+
if (throwable instanceof AssertionFailedError assertionFailedError) {
133+
return new RichDiffFormatter().format(assertionFailedError);
134+
}
126135
String message = throwable.getMessage();
127136
if (StringUtils.isBlank(message)) {
128137
message = throwable.toString();
129138
}
130-
printMessage(Style.FAILED, indent, message);
139+
return message;
131140
}
132141

133142
private void printReportEntry(String indent, ReportEntry reportEntry) {

platform-tests/src/test/java/org/junit/platform/console/output/RichDiffFormatterTests.java

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,74 @@
1919
class RichDiffFormatterTests {
2020

2121
@Test
22-
void test() {
23-
var expected = """
22+
void lineAdded() {
23+
assertRichDiffEquals("""
24+
{
25+
"speaker": "world"
26+
}
27+
""", """
2428
{
2529
"speaker": "world"
2630
"message": "hello"
2731
}
28-
""";
29-
var actuall = """
32+
""", """
33+
expected did not match actual
34+
+ actual - expected
35+
{
36+
"speaker": "world"
37+
+ "message": "hello"
38+
}
39+
""");
40+
}
41+
42+
@Test
43+
void lineRemoved() {
44+
assertRichDiffEquals("""
3045
{
31-
"speaker": "you"
46+
"speaker": "world"
3247
"message": "hello"
3348
}
34-
""";
49+
""", """
50+
{
51+
"speaker": "world"
52+
}
53+
""", """
54+
expected did not match actual
55+
+ actual - expected
56+
{
57+
"speaker": "world"
58+
- "message": "hello"
59+
}
60+
""");
61+
}
3562

36-
var assertionFailed = assertThrows(AssertionFailedError.class, () -> assertEquals(expected, actuall));
63+
@Test
64+
void lineChanged() {
65+
assertRichDiffEquals("""
66+
{
67+
"speaker": "world"
68+
"message": "hello"
69+
}
70+
""", """
71+
{
72+
"speaker": "you"
73+
"message": "hello"
74+
}
75+
""", """
76+
expected did not match actual
77+
+ actual - expected
78+
{
79+
+ "speaker": "world"
80+
- "speaker": "you"
81+
"message": "hello"
82+
}
83+
""");
84+
}
3785

86+
private static void assertRichDiffEquals(String expected, String actual, String expectedDiff) {
3887
var formatter = new RichDiffFormatter();
39-
40-
String message = formatter.format(assertionFailed);
41-
42-
assertEquals("""
43-
44-
""", message);
45-
88+
var assertionFailed = assertThrows(AssertionFailedError.class, () -> assertEquals(expected, actual));
89+
assertEquals(expectedDiff, formatter.format(assertionFailed));
4690
}
4791

4892
}

0 commit comments

Comments
 (0)