From 6dcc5daf7a94426904e9b0ea645518467d971f6b Mon Sep 17 00:00:00 2001 From: Stefan Bechtold Date: Tue, 18 Jul 2023 22:15:15 +0200 Subject: [PATCH] add a diff section to report of AssertionFailedError With this commit, a dependency to https://github.com/java-diff-utils/java-diff-utils is introduced. java-diff-utils is used to create a diff from the expected and the actual result and report it. See #3139 --- gradle/libs.versions.toml | 1 + .../junit-platform-console.gradle.kts | 6 +++- .../console/tasks/ConsoleTestExecutor.java | 3 ++ .../console/tasks/FlatPrintingListener.java | 34 +++++++++++++++++++ .../tasks/FlatPrintingListenerTests.java | 33 ++++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c9ad74561ca..9ccfb653d3e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ gradle-versions = { module = "com.github.ben-manes:gradle-versions-plugin", vers groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.12" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.21" } hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } +java-diff-utils = { module = "io.github.java-diff-utils:java-diff-utils", version = "4.12" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } jimfs = { module = "com.google.jimfs:jimfs", version = "1.2" } jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index 7893568a0967..a5a7f1022232 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -15,6 +15,8 @@ dependencies { compileOnly(libs.openTestReporting.events) + implementation(libs.java.diff.utils) + shadowed(libs.picocli) osgiVerification(projects.junitJupiterEngine) @@ -27,7 +29,9 @@ tasks { "--add-modules", "org.opentest4j.reporting.events", "--add-reads", "${project.projects.junitPlatformReporting.dependencyProject.javaModuleName}=org.opentest4j.reporting.events", "--add-modules", "info.picocli", - "--add-reads", "${javaModuleName}=info.picocli" + "--add-reads", "${javaModuleName}=info.picocli", + "--add-modules", "io.github.javadiffutils", + "--add-reads", "${javaModuleName}=io.github.javadiffutils" )) } shadowJar { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java index 694005933cdf..fc8e4df49d4a 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java @@ -21,6 +21,8 @@ import java.util.Optional; import java.util.function.Supplier; +import com.github.difflib.text.DiffRowGenerator; + import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ClassLoaderUtils; @@ -143,6 +145,7 @@ private SummaryGeneratingListener registerListeners(PrintWriter out, Optional createDetailsPrintingListener(PrintWriter out) { ColorPalette colorPalette = getColorPalette(); Theme theme = outputOptions.getTheme(); + switch (outputOptions.getDetails()) { case SUMMARY: // summary listener is always created and registered diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java index 304cedc27a2f..ce4b4dab4ae7 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java @@ -11,12 +11,20 @@ package org.junit.platform.console.tasks; import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import com.github.difflib.text.DiffRow; +import com.github.difflib.text.DiffRowGenerator; import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; +import org.opentest4j.AssertionFailedError; /** * @since 1.0 @@ -27,10 +35,19 @@ class FlatPrintingListener implements DetailsPrintingListener { private final PrintWriter out; private final ColorPalette colorPalette; + private final DiffRowGenerator diffRowGenerator; FlatPrintingListener(PrintWriter out, ColorPalette colorPalette) { this.out = out; this.colorPalette = colorPalette; + this.diffRowGenerator = DiffRowGenerator.create() // + .showInlineDiffs(true) // + .mergeOriginalRevised(true) // + .inlineDiffByWord(true) // + .oldTag(f -> "~") // + .newTag(f -> "**") // + .build(); + ; } @Override @@ -78,9 +95,26 @@ private void printlnTestDescriptor(Style style, String message, TestIdentifier t } private void printlnException(Style style, Throwable throwable) { + if (throwable instanceof AssertionFailedError) { + AssertionFailedError assertionFailedError = (AssertionFailedError) throwable; + String expected = assertionFailedError.getExpected().getStringRepresentation(); + String actual = assertionFailedError.getActual().getStringRepresentation(); + String diff = calculateDiff(expected, actual); + + printlnMessage(style, "Expected ", expected); + printlnMessage(style, "Actual ", actual); + printlnMessage(style, "Diff ", diff); + } printlnMessage(style, "Exception", ExceptionUtils.readStackTrace(throwable)); } + private String calculateDiff(String expected, String actual) { + List expectedLines = Arrays.asList(StringUtils.nullSafeToString(expected)); + List actualLines = Arrays.asList(StringUtils.nullSafeToString(actual)); + List diffRows = diffRowGenerator.generateDiffRows(expectedLines, actualLines); + return diffRows.stream().map(DiffRow::getOldLine).collect(Collectors.joining("\n")); + } + private void printlnMessage(Style style, String message, String detail) { println(style, INDENTATION + "=> " + message + ": %s", indented(detail)); } diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java index a79bd43f85ae..97d11d5e8d48 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java @@ -26,6 +26,7 @@ import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; +import org.opentest4j.AssertionFailedError; /** * @since 1.0 @@ -71,6 +72,38 @@ void executionFinishedWithFailure() { () -> assertEquals(INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", lines[1])); } + @Nested + class DiffOutputTests { + @Test + void printDiffForAssertionFailedErrors() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionFinished(newTestIdentifier(), + failed(new AssertionFailedError("Detail Message", "Expected content", "Actual content"))); + var lines = lines(stringWriter); + + assertTrue(lines.length >= 5, "At least 5 lines are expected in failure report!"); + assertAll("lines in the output", // + () -> assertEquals("Finished: demo-test ([engine:demo-engine])", lines[0]), // + () -> assertEquals(INDENTATION + "=> Expected : Expected content", lines[1]), // + () -> assertEquals(INDENTATION + "=> Actual : Actual content", lines[2]), // + () -> assertEquals(INDENTATION + "=> Diff : ~Expected~**Actual** content", lines[3]), // + () -> assertEquals(INDENTATION + "=> Exception: org.opentest4j.AssertionFailedError: Detail Message", + lines[4])); + } + + @Test + void ignoreDiffForAnyAssertionErrors() { + var stringWriter = new StringWriter(); + listener(stringWriter).executionFinished(newTestIdentifier(), failed(new AssertionError("Detail Message"))); + var lines = lines(stringWriter); + + assertTrue(lines.length >= 2, "At least 2 lines are expected in failure report!"); + assertAll("lines in the output", // + () -> assertEquals("Finished: demo-test ([engine:demo-engine])", lines[0]), // + () -> assertEquals(INDENTATION + "=> Exception: java.lang.AssertionError: Detail Message", lines[1])); + } + } + @Nested class ColorPaletteTests {