Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add diff section to report of AssertionFailedError failures #3397

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
* New `LauncherInterceptor` SPI
* New `testfeed` details mode for `ConsoleLauncher`
* New `ConsoleLauncher` subcommand for test discovery without execution
* `ConsoleLauncher` shows expected, actual, and a diff for failed assertions on `CharSequence` objects
* Dry-run mode for test execution
* New `NamespacedHierarchicalStore` for use in third-party test engines
* Stacktrace pruning to hide internal JUnit calls
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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" }
6 changes: 5 additions & 1 deletion junit-platform-console/junit-platform-console.gradle.kts
Original file line number Diff line number Diff line change
@@ -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 {
Original file line number Diff line number Diff line change
@@ -143,6 +143,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
Original file line number Diff line number Diff line change
@@ -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
@@ -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,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));
}
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
requires org.junit.platform.engine;
requires org.junit.platform.launcher;
requires org.junit.platform.reporting;
requires io.github.javadiffutils;

provides java.util.spi.ToolProvider with org.junit.platform.console.ConsoleLauncherToolProvider;
}
Original file line number Diff line number Diff line change
@@ -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,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 {