Skip to content

Commit

Permalink
add a diff section to report of AssertionFailedError
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bechte committed Jul 18, 2023
1 parent acb6e65 commit 4560a27
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 1 deletion.
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Expand Up @@ -41,6 +41,7 @@ gradle-versions = { module = "com.github.ben-manes:gradle-versions-plugin", vers
groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.13" }
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.3.0" }
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
Expand Down
6 changes: 5 additions & 1 deletion junit-platform-console/junit-platform-console.gradle.kts
Expand Up @@ -15,6 +15,8 @@ dependencies {

compileOnly(libs.openTestReporting.events)

implementation(libs.java.diff.utils)

shadowed(libs.picocli)

osgiVerification(projects.junitJupiterEngine)
Expand All @@ -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 {
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -143,6 +145,7 @@ private SummaryGeneratingListener registerListeners(PrintWriter out, Optional<Pa
private Optional<DetailsPrintingListener> createDetailsPrintingListener(PrintWriter out) {
ColorPalette colorPalette = getColorPalette();
Theme theme = outputOptions.getTheme();

switch (outputOptions.getDetails()) {
case SUMMARY:
// summary listener is always created and registered
Expand Down
Expand Up @@ -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.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;
import org.opentest4j.ValueWrapper;

/**
* @since 1.0
Expand All @@ -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
Expand Down Expand Up @@ -78,9 +95,31 @@ private void printlnTestDescriptor(Style style, String message, TestIdentifier t
}

private void printlnException(Style style, Throwable throwable) {
if (throwable instanceof AssertionFailedError) {
AssertionFailedError assertionFailedError = (AssertionFailedError) throwable;
ValueWrapper expected = assertionFailedError.getExpected();
ValueWrapper actual = assertionFailedError.getActual();

if (isCharSequence(expected) && isCharSequence(actual)) {
printlnMessage(style, "Expected ", expected.getStringRepresentation());
printlnMessage(style, "Actual ", actual.getStringRepresentation());
printlnMessage(style, "Diff ", calculateDiff(expected, actual));
}
}
printlnMessage(style, "Exception", ExceptionUtils.readStackTrace(throwable));
}

private boolean isCharSequence(ValueWrapper value) {
return value != null && CharSequence.class.isAssignableFrom(value.getType());
}

private String calculateDiff(ValueWrapper expected, ValueWrapper actual) {
List<String> expectedLines = Arrays.asList(expected.getStringRepresentation());
List<String> actualLines = Arrays.asList(actual.getStringRepresentation());
List<DiffRow> 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));
}
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -71,6 +72,52 @@ void executionFinishedWithFailure() {
() -> assertEquals(INDENTATION + "=> Exception: java.lang.AssertionError: Boom!", lines[1]));
}

@Nested
class DiffOutputTests {
@Test
void printDiffForStringsInAssertionFailedErrors() {
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 ignoreDiffForNumbersInAssertionFailedErrors() {
var stringWriter = new StringWriter();
listener(stringWriter).executionFinished(newTestIdentifier(),
failed(new AssertionFailedError("Detail Message", 10, 20)));
var lines = lines(stringWriter);

assertTrue(lines.length >= 2, "At least 3 lines are expected in failure report!");
assertAll("lines in the output", //
() -> assertEquals("Finished: demo-test ([engine:demo-engine])", lines[0]), //
() -> assertEquals(INDENTATION + "=> Exception: org.opentest4j.AssertionFailedError: Detail Message",
lines[1]));
}

@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 {

Expand Down

0 comments on commit 4560a27

Please sign in to comment.