Skip to content
Draft
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions documentation/documentation.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ val tools by sourceSets.creating
val toolsImplementation by configurations.getting

dependencies {
repositories {
// TODO: Remove
mavenLocal()
mavenCentral()
}

implementation(projects.junitJupiterApi) {
because("Jupiter API is used in src/main/java")
}
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ junit4 = "4.13.2"
junit4Min = "4.12"
ktlint = "1.8.0"
log4j = "2.25.3"
opentest4j = "1.3.0"
opentest4j = "1.4.0-SNAPSHOT"
openTestReporting = "0.2.5"
snapshotTests = "1.11.0"
surefire = "3.5.5"
Expand Down Expand Up @@ -67,6 +67,7 @@ openTestReporting-events = { module = "org.opentest4j.reporting:open-test-report
openTestReporting-tooling-core = { module = "org.opentest4j.reporting:open-test-reporting-tooling-core", version.ref = "openTestReporting" }
openTestReporting-tooling-spi = { module = "org.opentest4j.reporting:open-test-reporting-tooling-spi", version.ref = "openTestReporting" }
picocli = { module = "info.picocli:picocli", version = "4.7.7" }
javadiffutils = { module = "io.github.java-diff-utils:java-diff-utils", version = "4.15" }
roseau-cli = { module = "io.github.alien-tools:roseau-cli", version = "0.5.0" }
slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.17" }
snapshotTests-junit5 = { module = "de.skuzzle.test:snapshot-tests-junit5", version.ref = "snapshotTests" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,37 @@ public void buildAndThrow() throws AssertionFailedError {
*/
public AssertionFailedError build() {
String reason = nullSafeGet(this.reason);
String message = nullSafeGet(this.message);
var assertionFailedError = new AssertionFailedError( //
formatExceptionMessage(reason, message), //
formatReason(reason, message), //
expected, //
actual, //
cause //
);
maybeTrimStackTrace(assertionFailedError);
return assertionFailedError;
}

private @Nullable String formatReason(@Nullable String reason, @Nullable String message) {
if (mismatch) {
// TODO: The suffix is implicit due to mismatch, but how to make explicit?
reason = (reason == null ? "" : reason + ", ") + "expected did not match actual";
}
if (reason != null) {
message = buildPrefix(message) + reason;
}
return message;
}

private @Nullable String formatExceptionMessage(@Nullable String reason, @Nullable String message) {
if (mismatch && includeValuesInMessage) {
reason = (reason == null ? "" : reason + ", ") + formatValues(expected, actual);
}
String message = nullSafeGet(this.message);
if (reason != null) {
message = buildPrefix(message) + reason;
}

var assertionFailedError = mismatch //
? new AssertionFailedError(message, expected, actual, cause) //
: new AssertionFailedError(message, cause);

maybeTrimStackTrace(assertionFailedError);
return assertionFailedError;
return message;
}

private void maybeTrimStackTrace(Throwable throwable) {
Expand Down
5 changes: 4 additions & 1 deletion junit-platform-console/junit-platform-console.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies {
compileOnlyApi(libs.jspecify)

shadowed(libs.picocli)
// TODO: Sort out relocation, licence, ect
shadowed(libs.javadiffutils)

osgiVerification(projects.junitJupiterEngine)
osgiVerification(projects.junitPlatformLauncher)
Expand All @@ -29,7 +31,8 @@ tasks {
options.compilerArgs.addAll(listOf(
"-Xlint:-module", // due to qualified exports
"--add-modules", "info.picocli",
"--add-reads", "${javaModuleName}=info.picocli"
"--add-reads", "${javaModuleName}=info.picocli",
"-Xlint:-requires-automatic", // Java diff utils
))
options.errorprone.nullaway {
excludedFieldAnnotations.addAll(
Expand Down
1 change: 1 addition & 0 deletions junit-platform-console/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
requires org.junit.platform.engine;
requires org.junit.platform.launcher;
requires org.junit.platform.reporting;
requires io.github.javadiffutils;

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.console.output;

import java.util.function.Function;

import com.github.difflib.text.DiffRowGenerator;

import org.opentest4j.AssertionFailedError;

final class RichDiffFormatter {
public String format(AssertionFailedError assertionFailed) {
StringBuilder builder = new StringBuilder();

if (assertionFailed.isReasonDefined()) {
builder.append(": ");
builder.append(assertionFailed.getReason());
}
builder.append(System.lineSeparator());

builder.append("+ actual - expected");
builder.append(System.lineSeparator());

var generator = DiffRowGenerator.create() //
.lineNormalizer(Function.identity()) // Don't normalize lines
.showInlineDiffs(false) //
.build();

var diffRows = generator.generateDiffRows( //
assertionFailed.getExpected().getStringRepresentation().lines().toList(), //
assertionFailed.getActual().getStringRepresentation().lines().toList() //
);

diffRows.forEach(diffRow -> {
switch (diffRow.getTag()) {
case INSERT -> {
builder.append("+ ");
builder.append(diffRow.getNewLine());
builder.append(System.lineSeparator());
}
case DELETE -> {
builder.append("- ");
builder.append(diffRow.getOldLine());
builder.append(System.lineSeparator());
}
case CHANGE -> {
builder.append("+ ");
builder.append(diffRow.getOldLine());
builder.append(System.lineSeparator());
builder.append("- ");
builder.append(diffRow.getNewLine());
builder.append(System.lineSeparator());
}
case EQUAL -> {
builder.append(" ");
builder.append(diffRow.getNewLine());
builder.append(System.lineSeparator());
}
}
});

return builder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.junit.platform.engine.TestExecutionResult.Status;
import org.junit.platform.engine.reporting.FileEntry;
import org.junit.platform.engine.reporting.ReportEntry;
import org.opentest4j.AssertionFailedError;

/**
* @since 1.0
Expand Down Expand Up @@ -123,11 +124,19 @@ private void printThrowable(String indent, TestExecutionResult result) {
return;
}
Throwable throwable = result.getThrowable().get();
String message = formatThrowable(throwable);
printMessage(Style.FAILED, indent, message);
}

private static String formatThrowable(Throwable throwable) {
if (throwable instanceof AssertionFailedError assertionFailedError) {
return new RichDiffFormatter().format(assertionFailedError);
}
String message = throwable.getMessage();
if (StringUtils.isBlank(message)) {
message = throwable.toString();
}
printMessage(Style.FAILED, indent, message);
return message;
}

private void printReportEntry(String indent, ReportEntry reportEntry) {
Expand Down
5 changes: 5 additions & 0 deletions platform-tests/platform-tests.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ val woodstoxRuntimeClasspath = configurations.resolvable("woodstoxRuntimeClasspa
}

dependencies {
repositories {
mavenLocal()
mavenCentral()
}

// --- Things we are testing --------------------------------------------------
testImplementation(projects.junitPlatformCommons)
testImplementation(projects.junitPlatformConsole)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2015-2026 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.platform.console.output;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;

class RichDiffFormatterTests {

@Test
void lineAdded() {
assertRichDiffEquals("""
{
"speaker": "world"
}
""", """
{
"speaker": "world"
"message": "hello"
}
""", """
expected did not match actual
+ actual - expected
{
"speaker": "world"
+ "message": "hello"
}
""");
}

@Test
void lineRemoved() {
assertRichDiffEquals("""
{
"speaker": "world"
"message": "hello"
}
""", """
{
"speaker": "world"
}
""", """
expected did not match actual
+ actual - expected
{
"speaker": "world"
- "message": "hello"
}
""");
}

@Test
void lineChanged() {
assertRichDiffEquals("""
{
"speaker": "world"
"message": "hello"
}
""", """
{
"speaker": "you"
"message": "hello"
}
""", """
expected did not match actual
+ actual - expected
{
+ "speaker": "world"
- "speaker": "you"
"message": "hello"
}
""");
}

private static void assertRichDiffEquals(String expected, String actual, String expectedDiff) {
var formatter = new RichDiffFormatter();
var assertionFailed = assertThrows(AssertionFailedError.class, () -> assertEquals(expected, actual));
assertEquals(expectedDiff, formatter.format(assertionFailed));
}

}
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ plugins {
dependencyResolutionManagement {
repositories {
mavenCentral()
// TODO: Remove
mavenLocal()
}
}

Expand Down
Loading