Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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,6 +45,10 @@ 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<?>)` and `retainStackTraceElements(int)`
methods for `AssertionFailureBuilder`. It allows user defined assertions to
trim their stacktrace.
* `JAVA_27` has been added to the `JRE` enum for use with `JRE`-based execution conditions.


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,13 +10,16 @@

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;
import org.jspecify.annotations.Nullable;
import org.junit.platform.commons.util.Preconditions;
import org.junit.platform.commons.util.StringUtils;
import org.opentest4j.AssertionFailedError;

Expand All @@ -32,6 +35,8 @@
@API(status = STABLE, since = "5.9")
public class AssertionFailureBuilder {

private static final int DEFAULT_RETAIN_STACKTRACE_ELEMENTS = 1;

private @Nullable Object message;

private @Nullable Throwable cause;
Expand All @@ -46,6 +51,10 @@ public class AssertionFailureBuilder {

private boolean includeValuesInMessage = true;

private @Nullable Class<?> trimStackTraceTarget;

private int retainStackTraceElements = DEFAULT_RETAIN_STACKTRACE_ELEMENTS;

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

/**
* Set target to trim the stacktrace to.
*
* <p>Unless {@link #retainStackTraceElements(int)} is set all stacktrace
* elements before the last element from {@code target} are trimmed.
*
* @param target class to trim from the stacktrace
* @return this builder for method chaining
*/
@API(status = EXPERIMENTAL, since = "6.1")
public AssertionFailureBuilder trimStacktrace(@Nullable Class<?> target) {
this.trimStackTraceTarget = target;
return this;
}

/**
* Set depth to trim the stacktrace to. Defaults to
* {@value #DEFAULT_RETAIN_STACKTRACE_ELEMENTS}.
*
* <p>If {@link #trimStacktrace(Class)} was set, all but
* {@code retainStackTraceElements - 1} stacktrace elements before the last
* element from {@code target} are removed. If
* {@code retainStackTraceElements} is zero, all elements including those
* from {@code target} are trimmed.
*
* @param retainStackTraceElements depth of trimming, must be non-negative
* @return this builder for method chaining
*/
@API(status = EXPERIMENTAL, since = "6.1")
public AssertionFailureBuilder retainStackTraceElements(int retainStackTraceElements) {
Preconditions.condition(retainStackTraceElements >= 0,
"retainStackTraceElements must have a non-negative value");
this.retainStackTraceElements = retainStackTraceElements;
return this;
}

/**
* Build the {@link AssertionFailedError AssertionFailedError} and throw it.
*
Expand All @@ -154,9 +199,41 @@ 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 (trimStackTraceTarget == null) {
return;
}

var pruneTargetClassName = trimStackTraceTarget.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 = clamp0(lastIndexOf + 1 - retainStackTraceElements, stackTrace.length);
var trimmed = Arrays.copyOfRange(stackTrace, from, stackTrace.length);
throwable.setStackTrace(trimmed);
}
}

private static int clamp0(int value, int max) {
return Math.max(0, Math.min(value, max));
}

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
Loading
Loading