Skip to content

Commit ffd2a65

Browse files
committed
Render rich diffs for expected and actual values
1 parent d84ee58 commit ffd2a65

File tree

8 files changed

+111
-7
lines changed

8 files changed

+111
-7
lines changed

documentation/documentation.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ dependencies {
5757
repositories {
5858
// TODO: Remove
5959
mavenLocal()
60+
mavenCentral()
6061
}
61-
62+
6263
implementation(projects.junitJupiterApi) {
6364
because("Jupiter API is used in src/main/java")
6465
}

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ openTestReporting-events = { module = "org.opentest4j.reporting:open-test-report
6767
openTestReporting-tooling-core = { module = "org.opentest4j.reporting:open-test-reporting-tooling-core", version.ref = "openTestReporting" }
6868
openTestReporting-tooling-spi = { module = "org.opentest4j.reporting:open-test-reporting-tooling-spi", version.ref = "openTestReporting" }
6969
picocli = { module = "info.picocli:picocli", version = "4.7.7" }
70+
javadiffutils = { module = "io.github.java-diff-utils:java-diff-utils", version = "4.15" }
7071
roseau-cli = { module = "io.github.alien-tools:roseau-cli", version = "0.5.0" }
72+
roseau-cli = { module = "io.github.alien-tools:roseau-cli", version = "0.4.0" }
7173
slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.17" }
7274
snapshotTests-junit5 = { module = "de.skuzzle.test:snapshot-tests-junit5", version.ref = "snapshotTests" }
7375
snapshotTests-xml = { module = "de.skuzzle.test:snapshot-tests-xml", version.ref = "snapshotTests" }

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,19 @@ public void buildAndThrow() throws AssertionFailedError {
193193
public AssertionFailedError build() {
194194
String reason = nullSafeGet(this.reason);
195195
var assertionFailedError = new AssertionFailedError( //
196-
formatExceptionMessage(reason), //
197-
formatReason(reason), //
198-
expected, //
199-
actual, //
200-
cause //
196+
formatExceptionMessage(reason), //
197+
formatReason(reason), //
198+
expected, //
199+
actual, //
200+
cause //
201201
);
202202
maybeTrimStackTrace(assertionFailedError);
203203
return assertionFailedError;
204204
}
205205

206206
private @Nullable String formatReason(@Nullable String reason) {
207207
if (mismatch) {
208-
return (reason == null ? "" : reason + ", ") + "expected did not match actual";
208+
return (reason == null ? "" : reason + ", ") + "expected did not match actual";
209209
}
210210
return reason;
211211
}

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

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

2020
shadowed(libs.picocli)
21+
// TODO: Sort out
22+
shadowed(libs.javadiffutils)
2123

2224
osgiVerification(projects.junitJupiterEngine)
2325
osgiVerification(projects.junitPlatformLauncher)

junit-platform-console/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
requires org.junit.platform.engine;
2424
requires org.junit.platform.launcher;
2525
requires org.junit.platform.reporting;
26+
requires io.github.javadiffutils;
2627

2728
exports org.junit.platform.console.output to org.junit.start;
2829

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2015-2026 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.console.output;
12+
13+
import com.github.difflib.text.DiffRowGenerator;
14+
15+
import org.junit.platform.commons.util.ExceptionUtils;
16+
import org.opentest4j.AssertionFailedError;
17+
18+
final class RichDiffFormatter {
19+
public String format(AssertionFailedError assertionFailed) {
20+
if (!(assertionFailed.isActualDefined() && assertionFailed.isExpectedDefined())) {
21+
return ExceptionUtils.readStackTrace(assertionFailed);
22+
}
23+
24+
StringBuilder builder = new StringBuilder();
25+
26+
builder.append(assertionFailed.getClass().getSimpleName());
27+
if (assertionFailed.isReasonDefined()) {
28+
builder.append(": ");
29+
builder.append(assertionFailed.getReason());
30+
}
31+
builder.append(System.lineSeparator());
32+
33+
builder.append("+ actual - expected");
34+
builder.append(System.lineSeparator());
35+
36+
var generator = DiffRowGenerator.create().mergeOriginalRevised(true).build();
37+
38+
// TODO: But how to render the stacktrace?
39+
40+
builder.append(generator.generateDiffRows(assertionFailed.getExpected().toString().lines().toList(),
41+
assertionFailed.getActual().toString().lines().toList()));
42+
43+
return builder.toString();
44+
}
45+
}

platform-tests/platform-tests.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ val woodstoxRuntimeClasspath = configurations.resolvable("woodstoxRuntimeClasspa
3030
}
3131

3232
dependencies {
33+
repositories {
34+
mavenLocal()
35+
mavenCentral()
36+
}
37+
3338
// --- Things we are testing --------------------------------------------------
3439
testImplementation(projects.junitPlatformCommons)
3540
testImplementation(projects.junitPlatformConsole)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2015-2026 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.console.output;
12+
13+
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertThrows;
15+
16+
import org.junit.jupiter.api.Test;
17+
import org.opentest4j.AssertionFailedError;
18+
19+
class RichDiffFormatterTests {
20+
21+
@Test
22+
void test() {
23+
var expected = """
24+
{
25+
"speaker": "world"
26+
"message": "hello"
27+
}
28+
""";
29+
var actuall = """
30+
{
31+
"speaker": "you"
32+
"message": "hello"
33+
}
34+
""";
35+
36+
var assertionFailed = assertThrows(AssertionFailedError.class, () -> assertEquals(expected, actuall));
37+
38+
var formatter = new RichDiffFormatter();
39+
40+
String message = formatter.format(assertionFailed);
41+
42+
assertEquals("""
43+
44+
""", message);
45+
46+
}
47+
48+
}

0 commit comments

Comments
 (0)