diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts index 98e5b3c1a996..1d09a23998f6 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -54,6 +54,8 @@ tasks.withType().configureEach { enable() } isJSpecifyMode = true + customContractAnnotations.add("org.junit.platform.commons.annotation.Contract") + checkContracts = true } } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java index ea248480a299..d0e308d3be26 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertFalse} is a collection of utility methods that support asserting @@ -29,16 +30,19 @@ private AssertFalse() { /* no-op */ } + @Contract("true -> fail") static void assertFalse(boolean condition) { assertFalse(condition, (String) null); } + @Contract("true, _ -> fail") static void assertFalse(boolean condition, @Nullable String message) { if (condition) { failNotFalse(message); } } + @Contract("true, _ -> fail") static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { if (condition) { failNotFalse(messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java index 02f72aeffdc3..9586d350f7a8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertInstanceOf} is a collection of utility methods that support @@ -29,14 +30,17 @@ private AssertInstanceOf() { /* no-op */ } + @Contract("_, null -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return assertInstanceOf(expectedType, actualValue, (Object) null); } + @Contract("_, null, _ -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return assertInstanceOf(expectedType, actualValue, (Object) message); } + @Contract("_, null, _ -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, Supplier<@Nullable String> messageSupplier) { return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java index 1f1208f297dc..a03079f49341 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertNotNull} is a collection of utility methods that support asserting @@ -28,16 +29,19 @@ private AssertNotNull() { /* no-op */ } + @Contract("null -> fail") static void assertNotNull(@Nullable Object actual) { assertNotNull(actual, (String) null); } + @Contract("null, _ -> fail") static void assertNotNull(@Nullable Object actual, @Nullable String message) { if (actual == null) { failNull(message); } } + @Contract("null, _ -> fail") static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual == null) { failNull(messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java index 1acf4c1e6154..137fb1e83aee 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertNull} is a collection of utility methods that support asserting @@ -28,16 +29,19 @@ private AssertNull() { /* no-op */ } + @Contract("!null -> fail") static void assertNull(@Nullable Object actual) { assertNull(actual, (String) null); } + @Contract("!null, _ -> fail") static void assertNull(@Nullable Object actual, @Nullable String message) { if (actual != null) { failNotNull(actual, message); } } + @Contract("!null, _ -> fail") static void assertNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual != null) { failNotNull(actual, messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java index c106e08995e5..cb92e2278419 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertTrue} is a collection of utility methods that support asserting @@ -29,16 +30,19 @@ private AssertTrue() { /* no-op */ } + @Contract("false -> fail") static void assertTrue(boolean condition) { assertTrue(condition, (String) null); } + @Contract("false, _ -> fail") static void assertTrue(boolean condition, @Nullable String message) { if (!condition) { failNotTrue(message); } } + @Contract("false, _ -> fail") static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { if (!condition) { failNotTrue(messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java index 1d18b446f2f9..fb8f052bf2b5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java @@ -16,6 +16,7 @@ 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; @@ -31,22 +32,27 @@ private AssertionUtils() { /* no-op */ } + @Contract(" -> fail") static void fail() { throw new AssertionFailedError(); } + @Contract("_ -> fail") static void fail(@Nullable String message) { throw new AssertionFailedError(message); } + @Contract("_, _ -> fail") static void fail(@Nullable String message, @Nullable Throwable cause) { throw new AssertionFailedError(message, cause); } + @Contract("_ -> fail") static void fail(@Nullable Throwable cause) { throw new AssertionFailedError(null, cause); } + @Contract("_ -> fail") static void fail(Supplier<@Nullable String> messageSupplier) { throw new AssertionFailedError(nullSafeGet(messageSupplier)); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java index cc928e0eade9..845dd9b41604 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java @@ -26,6 +26,7 @@ import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.annotation.Contract; import org.opentest4j.MultipleFailuresError; /** @@ -116,6 +117,7 @@ protected Assertions() { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ + @Contract(" -> fail") @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail() { AssertionUtils.fail(); @@ -136,6 +138,7 @@ public static V fail() { * Stream.of().map(entry -> fail("should not be called")); * } */ + @Contract("_ -> fail") @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail(@Nullable String message) { AssertionUtils.fail(message); @@ -149,6 +152,7 @@ public static V fail(@Nullable String message) { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ + @Contract("_, _ -> fail") @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail(@Nullable String message, @Nullable Throwable cause) { AssertionUtils.fail(message, cause); @@ -161,6 +165,7 @@ public static V fail(@Nullable String message, @Nullable Throwable cause) { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ + @Contract("_ -> fail") @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail(@Nullable Throwable cause) { AssertionUtils.fail(cause); @@ -174,6 +179,7 @@ public static V fail(@Nullable Throwable cause) { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ + @Contract("_ -> fail") @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail(Supplier<@Nullable String> messageSupplier) { AssertionUtils.fail(messageSupplier); @@ -185,6 +191,7 @@ public static V fail(Supplier<@Nullable String> messageSupplier) { /** * Assert that the supplied {@code condition} is {@code true}. */ + @Contract("false -> fail") public static void assertTrue(boolean condition) { AssertTrue.assertTrue(condition); } @@ -193,6 +200,7 @@ public static void assertTrue(boolean condition) { * Assert that the supplied {@code condition} is {@code true}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ + @Contract("false, _ -> fail") public static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { AssertTrue.assertTrue(condition, messageSupplier); } @@ -216,6 +224,7 @@ public static void assertTrue(BooleanSupplier booleanSupplier, @Nullable String * Assert that the supplied {@code condition} is {@code true}. *

Fails with the supplied failure {@code message}. */ + @Contract("false, _ -> fail") public static void assertTrue(boolean condition, @Nullable String message) { AssertTrue.assertTrue(condition, message); } @@ -233,6 +242,7 @@ public static void assertTrue(BooleanSupplier booleanSupplier, Supplier<@Nullabl /** * Assert that the supplied {@code condition} is {@code false}. */ + @Contract("true -> fail") public static void assertFalse(boolean condition) { AssertFalse.assertFalse(condition); } @@ -241,6 +251,7 @@ public static void assertFalse(boolean condition) { * Assert that the supplied {@code condition} is {@code false}. *

Fails with the supplied failure {@code message}. */ + @Contract("true, _ -> fail") public static void assertFalse(boolean condition, @Nullable String message) { AssertFalse.assertFalse(condition, message); } @@ -249,6 +260,7 @@ public static void assertFalse(boolean condition, @Nullable String message) { * Assert that the supplied {@code condition} is {@code false}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ + @Contract("true, _ -> fail") public static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { AssertFalse.assertFalse(condition, messageSupplier); } @@ -281,6 +293,7 @@ public static void assertFalse(BooleanSupplier booleanSupplier, Supplier<@Nullab /** * Assert that {@code actual} is {@code null}. */ + @Contract("!null -> fail") public static void assertNull(@Nullable Object actual) { AssertNull.assertNull(actual); } @@ -289,6 +302,7 @@ public static void assertNull(@Nullable Object actual) { * Assert that {@code actual} is {@code null}. *

Fails with the supplied failure {@code message}. */ + @Contract("!null, _ -> fail") public static void assertNull(@Nullable Object actual, @Nullable String message) { AssertNull.assertNull(actual, message); } @@ -297,6 +311,7 @@ public static void assertNull(@Nullable Object actual, @Nullable String message) * Assert that {@code actual} is {@code null}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ + @Contract("!null, _ -> fail") public static void assertNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNull.assertNull(actual, messageSupplier); } @@ -306,6 +321,7 @@ public static void assertNull(@Nullable Object actual, Supplier<@Nullable String /** * Assert that {@code actual} is not {@code null}. */ + @Contract("null -> fail") public static void assertNotNull(@Nullable Object actual) { AssertNotNull.assertNotNull(actual); } @@ -314,6 +330,7 @@ public static void assertNotNull(@Nullable Object actual) { * Assert that {@code actual} is not {@code null}. *

Fails with the supplied failure {@code message}. */ + @Contract("null, _ -> fail") public static void assertNotNull(@Nullable Object actual, @Nullable String message) { AssertNotNull.assertNotNull(actual, message); } @@ -322,6 +339,7 @@ public static void assertNotNull(@Nullable Object actual, @Nullable String messa * Assert that {@code actual} is not {@code null}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ + @Contract("null, _ -> fail") public static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNotNull.assertNotNull(actual, messageSupplier); } @@ -3696,6 +3714,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @since 5.8 */ @API(status = STABLE, since = "5.10") + @Contract("_, null -> fail") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); } @@ -3712,6 +3731,7 @@ public static T assertInstanceOf(Class expectedType, @Nullable Object act * @since 5.8 */ @API(status = STABLE, since = "5.10") + @Contract("_, null, _ -> fail") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); @@ -3729,6 +3749,7 @@ public static T assertInstanceOf(Class expectedType, @Nullable Object act * * @since 5.8 */ + @Contract("_, null, _ -> fail") @API(status = STABLE, since = "5.10") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, Supplier<@Nullable String> messageSupplier) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java index 4e293a9fee4b..572f14b9de25 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java @@ -18,6 +18,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; import org.opentest4j.TestAbortedException; @@ -68,6 +69,7 @@ protected Assumptions() { * @param assumption the assumption to validate * @throws TestAbortedException if the assumption is not {@code true} */ + @Contract("false -> fail") public static void assumeTrue(boolean assumption) throws TestAbortedException { assumeTrue(assumption, "assumption is not true"); } @@ -103,6 +105,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, @Nullable Stri * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ + @Contract("false, _ -> fail") public static void assumeTrue(boolean assumption, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { if (!assumption) { @@ -118,6 +121,7 @@ public static void assumeTrue(boolean assumption, Supplier<@Nullable String> mes * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ + @Contract("false, _ -> fail") public static void assumeTrue(boolean assumption, @Nullable String message) throws TestAbortedException { if (!assumption) { throwAssumptionFailed(message); @@ -146,6 +150,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<@Null * @param assumption the assumption to validate * @throws TestAbortedException if the assumption is not {@code false} */ + @Contract("true -> fail") public static void assumeFalse(boolean assumption) throws TestAbortedException { assumeFalse(assumption, "assumption is not false"); } @@ -181,6 +186,7 @@ public static void assumeFalse(BooleanSupplier assumptionSupplier, @Nullable Str * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ + @Contract("true, _ -> fail") public static void assumeFalse(boolean assumption, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { if (assumption) { @@ -196,6 +202,7 @@ public static void assumeFalse(boolean assumption, Supplier<@Nullable String> me * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ + @Contract("true, _ -> fail") public static void assumeFalse(boolean assumption, @Nullable String message) throws TestAbortedException { if (assumption) { throwAssumptionFailed(message); @@ -277,6 +284,7 @@ public static void assumingThat(boolean assumption, Executable executable) { * @throws TestAbortedException always * @since 5.9 */ + @Contract(" -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort() { @@ -301,6 +309,7 @@ public static V abort() { * @throws TestAbortedException always * @since 5.9 */ + @Contract("_ -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(String message) { @@ -318,12 +327,14 @@ public static V abort(String message) { * @throws TestAbortedException always * @since 5.9 */ + @Contract("_ -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(Supplier messageSupplier) { throw new TestAbortedException(messageSupplier.get()); } + @Contract("_ -> fail") private static void throwAssumptionFailed(@Nullable String message) { throw new TestAbortedException( StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index c4de9b5bcd84..dbdb8539ebf1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -70,7 +70,6 @@ public static ConditionEvaluationResult disabled(@Nullable String reason) { * @see StringUtils#isBlank(String) */ @API(status = STABLE, since = "5.7") - @SuppressWarnings("NullAway") // StringUtils.isBlank() does not yet have a nullability @Contract public static ConditionEvaluationResult disabled(@Nullable String reason, @Nullable String customReason) { if (StringUtils.isBlank(reason)) { return disabled(customReason); @@ -85,7 +84,6 @@ public static ConditionEvaluationResult disabled(@Nullable String reason, @Nulla private final Optional reason; - @SuppressWarnings("NullAway") // StringUtils.isNotBlank() does not yet have a nullability @Contract private ConditionEvaluationResult(boolean enabled, @Nullable String reason) { this.enabled = enabled; this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java new file mode 100644 index 000000000000..af449e98d524 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2025 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.commons.annotation; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * Specifies some aspects of the annotated method's behavior to be used by tools + * for data flow analysis. + * + * @since 6.0 + * @see org.jetbrains.annotations.Contract + * @see NullAway custom contract annotations + */ +@Documented +@Target(ElementType.METHOD) +@API(status = INTERNAL, since = "6.0") +public @interface Contract { + + /** + * Contains the contract clauses describing causal relations between call + * arguments and the returned value. + */ + String value(); + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java index 7ac71abb59dc..9cb26236e463 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java @@ -24,6 +24,7 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.annotation.Contract; /** * A container object which may either contain a nullable value in case of @@ -364,11 +365,13 @@ public Try orElse(Supplier> supplier) { return Try.of(supplier::get); } + @Contract(" -> fail") @Override public V get() throws Exception { throw this.cause; } + @Contract("_ -> fail") @Override public V getOrThrow(Function exceptionTransformer) throws E { checkNotNull(exceptionTransformer, "exceptionTransformer"); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java index 97e245989f26..32bd731b217e 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java @@ -24,6 +24,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; @@ -70,6 +71,7 @@ private AnnotationSupport() { * @see #findRepeatableAnnotations(Optional, Class) */ @API(status = MAINTAINED, since = "1.3") + @Contract("null, _ -> false") @SuppressWarnings("NullableOptional") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { @@ -94,6 +96,7 @@ public static boolean isAnnotated(@Nullable Optional * @see #findAnnotation(AnnotatedElement, Class) * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ + @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java index fcf4ece0978a..be312dc7986d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java @@ -38,6 +38,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; /** @@ -76,6 +77,7 @@ private AnnotationUtils() { * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(Optional, Class) */ @SuppressWarnings("NullableOptional") + @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { @@ -102,6 +104,7 @@ public static boolean isAnnotated(Parameter parameter, int index, Class false") public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return findAnnotation(element, annotationType).isPresent(); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java index 3f68495d2393..cb20454d6fc1 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java @@ -25,6 +25,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for working with exceptions. @@ -69,6 +70,7 @@ private ExceptionUtils() { * returns anything; the return type is merely present to allow this * method to be supplied as the operand in a {@code throw} statement */ + @Contract("_ -> fail") public static RuntimeException throwAsUncheckedException(Throwable t) { Preconditions.notNull(t, "Throwable must not be null"); // The following line will never actually return an exception but rather @@ -76,6 +78,7 @@ public static RuntimeException throwAsUncheckedException(Throwable t) { return ExceptionUtils.throwAs(t); } + @Contract("_ -> fail") @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" }) private static T throwAs(Throwable t) throws T { throw (T) t; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java index c5e3e502c7c7..7032f0d739d7 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java @@ -19,6 +19,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for asserting preconditions for method and @@ -51,10 +52,9 @@ private Preconditions() { * @throws PreconditionViolationException if the supplied object is {@code null} * @see #notNull(Object, Supplier) */ + @Contract("null, _ -> fail; !null, _ -> param1") public static T notNull(@Nullable T object, String message) throws PreconditionViolationException { - if (object == null) { - throw new PreconditionViolationException(message); - } + condition(object != null, message); return object; } @@ -67,12 +67,11 @@ public static T notNull(@Nullable T object, String message) throws Precondit * @throws PreconditionViolationException if the supplied object is {@code null} * @see #condition(boolean, Supplier) */ + @Contract("null, _ -> fail; !null, _ -> param1") public static T notNull(@Nullable T object, Supplier messageSupplier) throws PreconditionViolationException { - if (object == null) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(object != null, messageSupplier); return object; } @@ -87,11 +86,10 @@ public static T notNull(@Nullable T object, Supplier messageSupplier * @since 1.9 * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") @API(status = INTERNAL, since = "1.11") public static int[] notEmpty(int @Nullable [] array, String message) throws PreconditionViolationException { - if (array == null || array.length == 0) { - throw new PreconditionViolationException(message); - } + condition(array != null && array.length > 0, message); return array; } @@ -109,10 +107,9 @@ public static int[] notEmpty(int @Nullable [] array, String message) throws Prec * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static T[] notEmpty(T @Nullable [] array, String message) throws PreconditionViolationException { - if (array == null || array.length == 0) { - throw new PreconditionViolationException(message); - } + condition(array != null && array.length > 0, message); return array; } @@ -130,12 +127,11 @@ public static T[] notEmpty(T @Nullable [] array, String message) throws Prec * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static T[] notEmpty(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { - if (array == null || array.length == 0) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(array != null && array.length > 0, messageSupplier); return array; } @@ -152,12 +148,11 @@ public static T[] notEmpty(T @Nullable [] array, Supplier messageSup * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static > T notEmpty(@Nullable T collection, String message) throws PreconditionViolationException { - if (collection == null || collection.isEmpty()) { - throw new PreconditionViolationException(message); - } + condition(collection != null && !collection.isEmpty(), message); return collection; } @@ -174,12 +169,11 @@ public static > T notEmpty(@Nullable T collection, Strin * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static > T notEmpty(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { - if (collection == null || collection.isEmpty()) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(collection != null && !collection.isEmpty(), messageSupplier); return collection; } @@ -197,6 +191,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static T @Nullable [] containsNoNullElements(T @Nullable [] array, String message) throws PreconditionViolationException { @@ -219,6 +214,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * any {@code null} elements * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static T @Nullable [] containsNoNullElements(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { @@ -241,6 +237,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * any {@code null} elements * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static > @Nullable T containsNoNullElements(@Nullable T collection, String message) throws PreconditionViolationException { @@ -263,6 +260,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * any {@code null} elements * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static > @Nullable T containsNoNullElements(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { @@ -284,10 +282,9 @@ public static > T notEmpty(@Nullable T collection, Suppl * @throws PreconditionViolationException if the supplied string is blank * @see #notBlank(String, Supplier) */ + @Contract("null, _ -> fail") public static String notBlank(@Nullable String str, String message) throws PreconditionViolationException { - if (str == null || StringUtils.isBlank(str)) { - throw new PreconditionViolationException(message); - } + condition(StringUtils.isNotBlank(str), message); return str; } @@ -304,12 +301,11 @@ public static String notBlank(@Nullable String str, String message) throws Preco * @see StringUtils#isNotBlank(String) * @see #condition(boolean, Supplier) */ + @Contract("null, _ -> fail") public static String notBlank(@Nullable String str, Supplier messageSupplier) throws PreconditionViolationException { - if (str == null || StringUtils.isBlank(str)) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(StringUtils.isNotBlank(str), messageSupplier); return str; } @@ -321,6 +317,7 @@ public static String notBlank(@Nullable String str, Supplier messageSupp * @throws PreconditionViolationException if the predicate is {@code false} * @see #condition(boolean, Supplier) */ + @Contract("false, _ -> fail") public static void condition(boolean predicate, String message) throws PreconditionViolationException { if (!predicate) { throw new PreconditionViolationException(message); @@ -334,6 +331,7 @@ public static void condition(boolean predicate, String message) throws Precondit * @param messageSupplier precondition violation message supplier * @throws PreconditionViolationException if the predicate is {@code false} */ + @Contract("false, _ -> fail") public static void condition(boolean predicate, Supplier messageSupplier) throws PreconditionViolationException { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java index d20052a7bf2f..1fd222935030 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java @@ -21,6 +21,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for working with {@link String Strings}, @@ -69,6 +70,7 @@ private StringUtils() { * @see String#isBlank() * @see #isNotBlank(String) */ + @Contract("null -> true") public static boolean isBlank(@Nullable String str) { return (str == null || str.isBlank()); } @@ -81,6 +83,7 @@ public static boolean isBlank(@Nullable String str) { * @return {@code true} if the string is not blank * @see #isBlank(String) */ + @Contract("null -> false") public static boolean isNotBlank(@Nullable String str) { return !isBlank(str); } @@ -93,6 +96,7 @@ public static boolean isNotBlank(@Nullable String str) { * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ + @Contract("null -> false") public static boolean containsWhitespace(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isWhitespace); } @@ -107,6 +111,7 @@ public static boolean containsWhitespace(@Nullable String str) { * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ + @Contract("null -> true") public static boolean doesNotContainWhitespace(@Nullable String str) { return !containsWhitespace(str); } @@ -119,6 +124,7 @@ public static boolean doesNotContainWhitespace(@Nullable String str) { * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ + @Contract("null -> false") public static boolean containsIsoControlCharacter(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isISOControl); } @@ -133,6 +139,7 @@ public static boolean containsIsoControlCharacter(@Nullable String str) { * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ + @Contract("null -> true") public static boolean doesNotContainIsoControlCharacter(@Nullable String str) { return !containsIsoControlCharacter(str); } @@ -241,6 +248,7 @@ public static String defaultToString(@Nullable Object obj) { * @since 1.4 */ @API(status = INTERNAL, since = "1.4") + @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable String replaceIsoControlCharacters(@Nullable String str, String replacement) { Preconditions.notNull(replacement, "replacement must not be null"); return str == null ? null : ISO_CONTROL_PATTERN.matcher(str).replaceAll(replacement); @@ -256,6 +264,7 @@ public static String defaultToString(@Nullable Object obj) { * @since 1.4 */ @API(status = INTERNAL, since = "1.4") + @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable String replaceWhitespaceCharacters(@Nullable String str, String replacement) { Preconditions.notNull(replacement, "replacement must not be null"); return str == null ? null : WHITESPACE_PATTERN.matcher(str).replaceAll(replacement); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java index 03de24ef6bd4..6858069246a2 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java @@ -10,8 +10,6 @@ package org.junit.platform.console.tasks; -import static java.util.Objects.requireNonNull; - import java.util.Optional; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -97,10 +95,11 @@ Optional identifier() { return Optional.ofNullable(identifier); } + @SuppressWarnings("DataFlowIssue") static String createCaption(String displayName) { boolean normal = displayName.length() <= 80; String caption = normal ? displayName : displayName.substring(0, 80) + "..."; String whites = StringUtils.replaceWhitespaceCharacters(caption, " "); - return requireNonNull(StringUtils.replaceIsoControlCharacters(whites, ".")); + return StringUtils.replaceIsoControlCharacters(whites, "."); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java index f6df0f45c825..edf14e43a249 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java @@ -10,7 +10,6 @@ package org.junit.platform.console.tasks; -import static java.util.Objects.requireNonNull; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.io.PrintWriter; @@ -129,7 +128,7 @@ private void printThrowable(String indent, TestExecutionResult result) { if (StringUtils.isBlank(message)) { message = throwable.toString(); } - printMessage(Style.FAILED, indent, requireNonNull(message)); + printMessage(Style.FAILED, indent, message); } private void printReportEntry(String indent, ReportEntry reportEntry) { diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java index d9e11b931e46..9b0d216a2d59 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java @@ -14,7 +14,6 @@ import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static java.util.Collections.emptyList; import static java.util.Comparator.naturalOrder; -import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; @@ -244,7 +243,7 @@ private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, private void writeSkippedElement(@Nullable String reason, XMLStreamWriter writer) throws XMLStreamException { if (isNotBlank(reason)) { writer.writeStartElement("skipped"); - writeCDataSafely(requireNonNull(reason)); + writeCDataSafely(reason); writer.writeEndElement(); } else { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java index d9ad9ea0ac73..638eb2f0761a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java @@ -55,7 +55,6 @@ public VintageTestDescriptor(UniqueId uniqueId, Description description, @Nullab this.description = description; } - @SuppressWarnings("NullAway") private static String generateDisplayName(Description description) { String methodName = DescriptionUtils.getMethodName(description); return isNotBlank(methodName) ? methodName : description.getDisplayName();