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
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ repository on GitHub.
[[release-notes-6.1.0-M2-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements

*

* Trim internal stack frames from `AssertionFailedError` stack traces.
* Introduce new `trimStacktrace(Class<?>)` method for `AssertionFailureBuilder`. It allows user defined assertions to trim their stacktrace.

[[release-notes-6.1.0-M2-junit-vintage]]
=== JUnit Vintage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ private static AssertionFailedError expectedArrayIsNullFailure(@Nullable Deque<I
return assertionFailure() //
.message(messageOrSupplier) //
.reason("expected array was <null>" + formatIndexes(indexes)) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand All @@ -495,6 +496,7 @@ private static AssertionFailedError actualArrayIsNullFailure(@Nullable Deque<Int
return assertionFailure() //
.message(messageOrSupplier) //
.reason("actual array was <null>" + formatIndexes(indexes)) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand All @@ -507,6 +509,7 @@ private static void assertArraysHaveSameLength(int expected, int actual, @Nullab
.reason("array lengths differ" + formatIndexes(indexes)) //
.expected(expected) //
.actual(actual) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}
}
Expand All @@ -519,6 +522,7 @@ private static void failArraysNotEqual(@Nullable Object expected, @Nullable Obje
.reason("array contents differ" + formatIndexes(indexes)) //
.expected(expected) //
.actual(actual) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static AssertionFailedError createAssertionFailedError(@Nullable Object m
.message(messageOrSupplier) //
.reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) //
.cause(t) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ private static void failNotEqual(@Nullable Object expected, @Nullable Object act
.message(messageOrSupplier) //
.expected(expected) //
.actual(actual) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ private static void failNotFalse(@Nullable Object messageOrSupplier) {
.message(messageOrSupplier) //
.expected(false) //
.actual(true) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ private static <T> T assertInstanceOf(Class<T> expectedType, @Nullable Object ac
.expected(expectedType) //
.actual(actualValue == null ? null : actualValue.getClass()) //
.cause(actualValue instanceof Throwable t ? t : null) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}
return expectedType.cast(actualValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ private static AssertionFailedError expectedIterableIsNullFailure(Deque<Integer>
return assertionFailure() //
.message(messageOrSupplier) //
.reason("expected iterable was <null>" + formatIndexes(indexes)) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand All @@ -165,6 +166,7 @@ private static AssertionFailedError actualIterableIsNullFailure(Deque<Integer> i
return assertionFailure() //
.message(messageOrSupplier) //
.reason("actual iterable was <null>" + formatIndexes(indexes)) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ void fail(String format, Object... args) {
.expected(join(newLine, expectedLines)) //
.actual(join(newLine, actualLines)) //
.includeValuesInMessage(false) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ private static void failEqual(@Nullable Object actual, @Nullable Object messageO
assertionFailure() //
.message(messageOrSupplier) //
.reason("expected: not equal but was: <" + actual + ">") //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ private static void failNull(@Nullable Object messageOrSupplier) {
assertionFailure() //
.message(messageOrSupplier) //
.reason("expected: not <null>") //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ private static void failSame(@Nullable Object actual, @Nullable Object messageOr
assertionFailure() //
.message(messageOrSupplier) //
.reason("expected: not same but was: <" + actual + ">") //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private static void failNotNull(@Nullable Object actual, @Nullable Object messag
.message(messageOrSupplier) //
.expected(null) //
.actual(actual) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private static void failNotSame(@Nullable Object expected, @Nullable Object actu
.message(messageOrSupplier) //
.expected(expected) //
.actual(actual) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ private static <T extends Throwable> T assertThrows(Class<T> expectedType, Execu
.actual(actualException.getClass()) //
.reason("Unexpected exception type thrown") //
.cause(actualException) //
.trimStacktrace(Assertions.class) //
.build();
}
}
throw assertionFailure() //
.message(messageOrSupplier) //
.reason("Expected %s to be thrown, but nothing was thrown.".formatted(getCanonicalName(expectedType))) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ private static <T extends Throwable> T assertThrowsExactly(Class<T> expectedType
.actual(actualException.getClass()) //
.reason("Unexpected exception type thrown") //
.cause(actualException) //
.trimStacktrace(Assertions.class) //
.build();
}
}

throw assertionFailure() //
.message(messageOrSupplier) //
.reason("Expected %s to be thrown, but nothing was thrown.".formatted(getCanonicalName(expectedType))) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ static void assertTimeout(Duration timeout, Executable executable, Supplier<@Nul
.message(messageOrSupplier) //
.reason("execution exceeded timeout of " + timeoutInMillis + " ms by "
+ (timeElapsed - timeoutInMillis) + " ms") //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ private static AssertionFailedError createAssertionFailure(Duration timeout,
.message(messageSupplier) //
.reason("execution timed out after " + timeout.toMillis() + " ms") //
.cause(cause) //
.trimStacktrace(Assertions.class) //
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ private static void failNotTrue(@Nullable Object messageOrSupplier) {
.message(messageOrSupplier) //
.expected(true) //
.actual(false) //
.trimStacktrace(Assertions.class) //
.buildAndThrow();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

package org.junit.jupiter.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;
import static org.junit.jupiter.api.AssertionUtils.getCanonicalName;

import java.util.Arrays;
import java.util.function.Supplier;

import org.apiguardian.api.API;
Expand Down Expand Up @@ -46,6 +48,8 @@ public class AssertionFailureBuilder {

private boolean includeValuesInMessage = true;

private @Nullable Class<?> trimStackTraceTo;

/**
* Create a new {@code AssertionFailureBuilder}.
*/
Expand Down Expand Up @@ -130,6 +134,21 @@ public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMes
return this;
}

/**
* Set class to trim from the stacktrace.
*
* <p>To improve the readability of assertion failures, stack-frames up to
* and including any frames from {@code to} are trimmed from the stacktrace.
*
* @param to class to prune from the stacktrace.
* @return this builder for method chaining
*/
@API(status = EXPERIMENTAL, since = "6.1")
public AssertionFailureBuilder trimStacktrace(@Nullable Class<?> to) {
this.trimStackTraceTo = to;
return this;
}

/**
* Build the {@link AssertionFailedError AssertionFailedError} and throw it.
*
Expand All @@ -154,9 +173,37 @@ public AssertionFailedError build() {
if (reason != null) {
message = buildPrefix(message) + reason;
}
return mismatch //

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

maybeTrimStackTrace(assertionFailedError);
return assertionFailedError;
}

private void maybeTrimStackTrace(Throwable throwable) {
if (trimStackTraceTo == null) {
return;
}

var pruneTargetClassName = trimStackTraceTo.getName();
var stackTrace = throwable.getStackTrace();

int lastIndexOf = -1;
for (int i = 0; i < stackTrace.length; i++) {
var element = stackTrace[i];
var className = element.getClassName();
if (className.equals(pruneTargetClassName)) {
lastIndexOf = i;
}
}

if (lastIndexOf != -1) {
int from = Math.min(lastIndexOf + 1, stackTrace.length);
var pruned = Arrays.copyOfRange(stackTrace, from, stackTrace.length);
throwable.setStackTrace(pruned);
}
}

private static @Nullable String nullSafeGet(@Nullable Object messageOrSupplier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
package org.junit.jupiter.api;

import static java.util.stream.Collectors.joining;
import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure;

import java.util.Deque;
import java.util.function.Supplier;

import org.jspecify.annotations.Nullable;
import org.junit.platform.commons.annotation.Contract;
import org.junit.platform.commons.util.UnrecoverableExceptions;
import org.opentest4j.AssertionFailedError;

/**
* {@code AssertionUtils} is a collection of utility methods that are common to
Expand All @@ -34,27 +34,42 @@ private AssertionUtils() {

@Contract(" -> fail")
static void fail() {
throw new AssertionFailedError();
throw assertionFailure() //
.trimStacktrace(Assertions.class) //
.build();
}

@Contract("_ -> fail")
static void fail(@Nullable String message) {
throw new AssertionFailedError(message);
throw assertionFailure() //
.message(message) //
.trimStacktrace(Assertions.class) //
.build();
}

@Contract("_, _ -> fail")
static void fail(@Nullable String message, @Nullable Throwable cause) {
throw new AssertionFailedError(message, cause);
throw assertionFailure() //
.message(message) //
.cause(cause) //
.trimStacktrace(Assertions.class) //
.build();
}

@Contract("_ -> fail")
static void fail(@Nullable Throwable cause) {
throw new AssertionFailedError(null, cause);
throw assertionFailure() //
.cause(cause) //
.trimStacktrace(Assertions.class) //
.build();
}

@Contract("_ -> fail")
static void fail(Supplier<@Nullable String> messageSupplier) {
throw new AssertionFailedError(nullSafeGet(messageSupplier));
throw assertionFailure() //
.message(nullSafeGet(messageSupplier)) //
.trimStacktrace(Assertions.class) //
.build();
}

static @Nullable String nullSafeGet(@Nullable Supplier<@Nullable String> messageSupplier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ inline fun <R> assertDoesNotThrow(executable: () -> R): R {
throw assertionFailure()
.reason("Unexpected exception thrown: ${t.javaClass.getName()}$suffix")
.cause(t)
.trimStacktrace(AssertionFailureBuilder::class.java)
.build()
}
}
Expand Down Expand Up @@ -420,6 +421,7 @@ inline fun <R> assertDoesNotThrow(
.message(message())
.reason("Unexpected exception thrown: ${t.javaClass.getName()}$suffix")
.cause(t)
.trimStacktrace(AssertionFailureBuilder::class.java)
.build()
}
}
Expand Down
Loading