diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 072b521a24c5..e4184c29e388 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -120,6 +120,17 @@ See [`ExtensionContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/e [`ParameterContext`](junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java) for example Javadoc. +### Nullability + +This project uses JSpecify's annotation to indicate nullability. In general, the approach +is as follows: + +- The Gradle build is set up to treat all code as being `@NullMarked` +- The descriptor of each module is annotated with `@NullMarked` for IDEs such as IntelliJ + IDEA to treat code correctly. +- Fields, parameters, return types etc. may be annotated with `@Nullable` +- A package can be excluded (temporarily) using `@NullUnmarked` + ### Tests #### Naming diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index e079ac81867e..2c34e5fda5e5 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -18,6 +18,7 @@ plugins { alias(libs.plugins.plantuml) id("junitbuild.build-parameters") id("junitbuild.java-multi-release-test-sources") + id("junitbuild.java-nullability-conventions") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") } diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc index c90775899d35..9de46921d436 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc @@ -27,7 +27,8 @@ repository on GitHub. [[release-notes-6.0.0-M1-overall-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* All JUnit modules now use https://jspecify.dev/[JSpecify]'s nullability annotations to + indicate which method parameters, return types, etc. can be `null`. [[release-notes-6.0.0-M1-junit-platform]] diff --git a/documentation/src/main/java/example/util/StringUtils.java b/documentation/src/main/java/example/util/StringUtils.java index a48d72e3d3ad..b0eee5e142f8 100644 --- a/documentation/src/main/java/example/util/StringUtils.java +++ b/documentation/src/main/java/example/util/StringUtils.java @@ -10,10 +10,14 @@ package example.util; +import static java.util.Objects.requireNonNull; + +import org.jspecify.annotations.Nullable; + public class StringUtils { - public static boolean isPalindrome(String candidate) { - int length = candidate.length(); + public static boolean isPalindrome(@Nullable String candidate) { + int length = requireNonNull(candidate).length(); for (int i = 0; i < length / 2; i++) { if (candidate.charAt(i) != candidate.charAt(length - (i + 1))) { return false; diff --git a/documentation/src/test/java/example/ClassTemplateDemo.java b/documentation/src/test/java/example/ClassTemplateDemo.java index d0291dedfbdf..d1035b434348 100644 --- a/documentation/src/test/java/example/ClassTemplateDemo.java +++ b/documentation/src/test/java/example/ClassTemplateDemo.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; @@ -37,6 +38,9 @@ class ClassTemplateDemo { // tag::custom_line_break[] = unmodifiableList(Arrays.asList("apple", "banana", "lemon")); + //end::user_guide[] + @Nullable + //tag::user_guide[] private String fruit; @Test diff --git a/documentation/src/test/java/example/DynamicTestsDemo.java b/documentation/src/test/java/example/DynamicTestsDemo.java index c5643890b2d3..f6adb6b2117b 100644 --- a/documentation/src/test/java/example/DynamicTestsDemo.java +++ b/documentation/src/test/java/example/DynamicTestsDemo.java @@ -47,7 +47,7 @@ class DynamicTestsDemo { @TestFactory // end::user_guide[] @Tag("exclude") - DynamicTest dummy() { return null; } + DynamicTest dummy() { return dynamicTest("dummy", () -> {}); } // tag::user_guide[] List dynamicTestsWithInvalidReturnType() { return Arrays.asList("Hello"); diff --git a/documentation/src/test/java/example/FirstCustomEngine.java b/documentation/src/test/java/example/FirstCustomEngine.java index efd9b14a7f0c..5fd886fd65e8 100644 --- a/documentation/src/test/java/example/FirstCustomEngine.java +++ b/documentation/src/test/java/example/FirstCustomEngine.java @@ -18,6 +18,7 @@ import java.io.UncheckedIOException; import java.net.ServerSocket; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -32,6 +33,9 @@ */ public class FirstCustomEngine implements TestEngine { + //end::user_guide[] + @Nullable + //tag::user_guide[] public ServerSocket socket; @Override diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index b76647dc6861..a7312f584cc5 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -459,6 +459,7 @@ void testWithExplicitArgumentConversion( // end::explicit_conversion_example[] static + @SuppressWarnings({ "NullableProblems", "NullAway" }) // tag::explicit_conversion_example_ToStringArgumentConverter[] public class ToStringArgumentConverter extends SimpleArgumentConverter { @@ -474,6 +475,7 @@ protected Object convert(Object source, Class targetType) { // end::explicit_conversion_example_ToStringArgumentConverter[] static + @SuppressWarnings({ "NullableProblems", "NullAway", "ConstantValue" }) // tag::explicit_conversion_example_TypedArgumentConverter[] public class ToLengthArgumentConverter extends TypedArgumentConverter { diff --git a/documentation/src/test/java/example/SecondCustomEngine.java b/documentation/src/test/java/example/SecondCustomEngine.java index 3d11c13ac18d..ff90dfa8b983 100644 --- a/documentation/src/test/java/example/SecondCustomEngine.java +++ b/documentation/src/test/java/example/SecondCustomEngine.java @@ -17,6 +17,7 @@ import java.io.UncheckedIOException; import java.net.ServerSocket; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -32,6 +33,9 @@ */ public class SecondCustomEngine implements TestEngine { + //end::user_guide[] + @Nullable + //tag::user_guide[] public ServerSocket socket; @Override diff --git a/documentation/src/test/java/example/TestingAStackDemo.java b/documentation/src/test/java/example/TestingAStackDemo.java index 80b1ec495d83..c4ef3a5640dc 100644 --- a/documentation/src/test/java/example/TestingAStackDemo.java +++ b/documentation/src/test/java/example/TestingAStackDemo.java @@ -27,8 +27,6 @@ @DisplayName("A stack") class TestingAStackDemo { - Stack stack; - @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { @@ -39,6 +37,8 @@ void isInstantiatedWithNew() { @DisplayName("when new") class WhenNew { + Stack stack; + @BeforeEach void createNewStack() { stack = new Stack<>(); diff --git a/documentation/src/test/java/example/callbacks/package-info.java b/documentation/src/test/java/example/callbacks/package-info.java new file mode 100644 index 000000000000..a58f6d3a9a52 --- /dev/null +++ b/documentation/src/test/java/example/callbacks/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.callbacks; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/defaultmethods/package-info.java b/documentation/src/test/java/example/defaultmethods/package-info.java new file mode 100644 index 000000000000..851eeadc2f84 --- /dev/null +++ b/documentation/src/test/java/example/defaultmethods/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.defaultmethods; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/exception/package-info.java b/documentation/src/test/java/example/exception/package-info.java new file mode 100644 index 000000000000..5c758dfba6c9 --- /dev/null +++ b/documentation/src/test/java/example/exception/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.exception; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/extensions/HttpServerExtension.java b/documentation/src/test/java/example/extensions/HttpServerExtension.java index 5ba23509d964..33a447468790 100644 --- a/documentation/src/test/java/example/extensions/HttpServerExtension.java +++ b/documentation/src/test/java/example/extensions/HttpServerExtension.java @@ -28,12 +28,15 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon return HttpServer.class.equals(parameterContext.getParameter().getType()); } + //end::user_guide[] + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + //tag::user_guide[] @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { ExtensionContext rootContext = extensionContext.getRoot(); ExtensionContext.Store store = rootContext.getStore(Namespace.GLOBAL); - String key = HttpServerResource.class.getName(); + Class key = HttpServerResource.class; HttpServerResource resource = store.getOrComputeIfAbsent(key, __ -> { try { HttpServerResource serverResource = new HttpServerResource(0); diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java index e11b87ce5b52..6cdbb7cdb87b 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberDemo.java +++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -10,6 +10,7 @@ package example.extensions; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,6 +20,9 @@ class RandomNumberDemo { // Use static randomNumber0 field anywhere in the test class, // including @BeforeAll or @AfterEach lifecycle methods. @Random + // end::user_guide[] + @Nullable + // tag::user_guide[] private static Integer randomNumber0; // Use randomNumber1 field in test methods and @BeforeEach diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java index 666e6ffe9ae5..c3386b0972f1 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberExtension.java +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -17,6 +17,7 @@ import java.lang.reflect.Field; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; @@ -69,7 +70,7 @@ public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) { return this.random.nextInt(); } - private void injectFields(Class testClass, Object testInstance, + private void injectFields(Class testClass, @Nullable Object testInstance, Predicate predicate) { predicate = predicate.and(field -> isInteger(field.getType())); diff --git a/documentation/src/test/java/example/extensions/package-info.java b/documentation/src/test/java/example/extensions/package-info.java new file mode 100644 index 000000000000..df450c1a5811 --- /dev/null +++ b/documentation/src/test/java/example/extensions/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.extensions; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/interceptor/package-info.java b/documentation/src/test/java/example/interceptor/package-info.java new file mode 100644 index 000000000000..ece161c1da00 --- /dev/null +++ b/documentation/src/test/java/example/interceptor/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.interceptor; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/package-info.java b/documentation/src/test/java/example/package-info.java new file mode 100644 index 000000000000..a2bcf4a4db17 --- /dev/null +++ b/documentation/src/test/java/example/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/registration/DocumentationDemo.java b/documentation/src/test/java/example/registration/DocumentationDemo.java index 59571061d7de..7512235d9dc3 100644 --- a/documentation/src/test/java/example/registration/DocumentationDemo.java +++ b/documentation/src/test/java/example/registration/DocumentationDemo.java @@ -12,6 +12,7 @@ import java.nio.file.Path; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -20,6 +21,9 @@ //tag::user_guide[] class DocumentationDemo { + //end::user_guide[] + @Nullable + //tag::user_guide[] static Path lookUpDocsDir() { // return path to docs dir // end::user_guide[] @@ -40,13 +44,13 @@ void generateDocumentation() { class DocumentationExtension implements AfterEachCallback { @SuppressWarnings("unused") - private final Path path; + private final @Nullable Path path; - private DocumentationExtension(Path path) { + private DocumentationExtension(@Nullable Path path) { this.path = path; } - static DocumentationExtension forPath(Path path) { + static DocumentationExtension forPath(@Nullable Path path) { return new DocumentationExtension(path); } diff --git a/documentation/src/test/java/example/registration/package-info.java b/documentation/src/test/java/example/registration/package-info.java new file mode 100644 index 000000000000..ceb3eb919fc2 --- /dev/null +++ b/documentation/src/test/java/example/registration/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.registration; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/session/HttpTests.java b/documentation/src/test/java/example/session/HttpTests.java index 97a13439a3e4..ed68c283380f 100644 --- a/documentation/src/test/java/example/session/HttpTests.java +++ b/documentation/src/test/java/example/session/HttpTests.java @@ -49,6 +49,9 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon return HttpServer.class.equals(parameterContext.getParameter().getType()); } + //end::user_guide[] + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + //tag::user_guide[] @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return extensionContext diff --git a/documentation/src/test/java/example/session/package-info.java b/documentation/src/test/java/example/session/package-info.java new file mode 100644 index 000000000000..7f7249dfecf0 --- /dev/null +++ b/documentation/src/test/java/example/session/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.session; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java b/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java index 52b00c624471..4ca8e4a11efb 100644 --- a/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java +++ b/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java @@ -24,6 +24,7 @@ class SharedResourceDemo { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) //tag::user_guide[] @Test void runBothCustomEnginesTest() { diff --git a/documentation/src/test/java/example/sharedresources/package-info.java b/documentation/src/test/java/example/sharedresources/package-info.java new file mode 100644 index 000000000000..02e87d295042 --- /dev/null +++ b/documentation/src/test/java/example/sharedresources/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.sharedresources; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/testinterface/package-info.java b/documentation/src/test/java/example/testinterface/package-info.java new file mode 100644 index 000000000000..3c0a7b9e0712 --- /dev/null +++ b/documentation/src/test/java/example/testinterface/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.testinterface; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/testkit/package-info.java b/documentation/src/test/java/example/testkit/package-info.java new file mode 100644 index 000000000000..bb644c62acd1 --- /dev/null +++ b/documentation/src/test/java/example/testkit/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.testkit; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/example/timing/TimingExtension.java b/documentation/src/test/java/example/timing/TimingExtension.java index 1426e42629f1..f07abe049c3e 100644 --- a/documentation/src/test/java/example/timing/TimingExtension.java +++ b/documentation/src/test/java/example/timing/TimingExtension.java @@ -36,12 +36,15 @@ public class TimingExtension implements BeforeTestExecutionCallback, AfterTestEx private static final String START_TIME = "start time"; @Override - public void beforeTestExecution(ExtensionContext context) throws Exception { + public void beforeTestExecution(ExtensionContext context) { getStore(context).put(START_TIME, System.currentTimeMillis()); } + //end::user_guide[] + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + //tag::user_guide[] @Override - public void afterTestExecution(ExtensionContext context) throws Exception { + public void afterTestExecution(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); long startTime = getStore(context).remove(START_TIME, long.class); long duration = System.currentTimeMillis() - startTime; diff --git a/documentation/src/test/java/example/timing/package-info.java b/documentation/src/test/java/example/timing/package-info.java new file mode 100644 index 000000000000..545aa617da23 --- /dev/null +++ b/documentation/src/test/java/example/timing/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example.timing; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java/extensions/package-info.java b/documentation/src/test/java/extensions/package-info.java new file mode 100644 index 000000000000..edd479f63275 --- /dev/null +++ b/documentation/src/test/java/extensions/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package extensions; + +import org.jspecify.annotations.NullMarked; diff --git a/documentation/src/test/java21/example/ParameterizedLifecycleDemo.java b/documentation/src/test/java21/example/ParameterizedLifecycleDemo.java index c55f121cfb3f..c9c3375367d1 100644 --- a/documentation/src/test/java21/example/ParameterizedLifecycleDemo.java +++ b/documentation/src/test/java21/example/ParameterizedLifecycleDemo.java @@ -17,6 +17,7 @@ import java.nio.file.Path; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -53,6 +54,9 @@ static void beforeInvocation(TextFile textFile, @TempDir Path tempDir) throws Ex textFile.path = Files.writeString(filePath, textFile.content); } + //end::user_guide[] + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + //tag::user_guide[] @AfterParameterizedClassInvocation static void afterInvocation(TextFile textFile) throws Exception { var actualContent = Files.readString(textFile.path); // <3> @@ -61,6 +65,9 @@ static void afterInvocation(TextFile textFile) throws Exception { // File will be deleted automatically by @TempDir support } + //end::user_guide[] + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + //tag::user_guide[] @Test void test() { assertTrue(Files.exists(textFile.path)); // <2> @@ -75,6 +82,9 @@ static class TextFile { final String fileName; final String content; + // end::example[] + @Nullable + // tag::example[] Path path; TextFile(String fileName, String content) { diff --git a/documentation/src/test/java21/example/package-info.java b/documentation/src/test/java21/example/package-info.java new file mode 100644 index 000000000000..a2bcf4a4db17 --- /dev/null +++ b/documentation/src/test/java21/example/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package example; + +import org.jspecify.annotations.NullMarked; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ebc98dc48394..8cf8ca000653 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ ktlint = "1.6.0" log4j = "2.24.3" logback = "1.5.18" opentest4j = "1.3.0" -openTestReporting = "0.2.3" +openTestReporting = "0.3.0-SNAPSHOT" snapshotTests = "1.11.0" surefire = "3.5.3" xmlunit = "2.10.1" @@ -35,6 +35,7 @@ bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } commons-io = { module = "commons-io:commons-io", version = "2.19.0" } +errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.38.0" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.26" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } @@ -46,6 +47,7 @@ jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.0" } jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } joox = { module = "org.jooq:joox", version = "2.0.1" } +jspecify = { module = "org.jspecify:jspecify", version = "1.0.0" } jte = { module = "gg.jte:jte", version = "3.2.1" } junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2" } @@ -58,6 +60,7 @@ mockito-bom = { module = "org.mockito:mockito-bom", version = "5.18.0" } mockito-core = { module = "org.mockito:mockito-core" } mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter" } nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" } +nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.7" } opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" } openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } @@ -95,6 +98,7 @@ bnd = { id = "biz.aQute.bnd", version.ref = "bnd" } buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" } commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.2.1" } develocity = { id = "com.gradle.develocity", version = "4.0.1" } +errorProne = { id = "net.ltgt.errorprone", version = "4.2.0" } extraJavaModuleInfo = { id = "org.gradlex.extra-java-module-info", version = "1.12" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" } @@ -102,6 +106,7 @@ jmh = { id = "me.champeau.jmh", version = "0.7.3" } jreleaser = { id = "org.jreleaser", version = "1.18.0" } # check if workaround in gradle.properties can be removed when updating kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.21" } +nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } openrewrite = { id = "org.openrewrite.rewrite", version = "7.6.2" } plantuml = { id = "io.freefair.plantuml", version = "8.13.1" } shadow = { id = "com.gradleup.shadow", version = "8.3.6" } diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts index 5f31236d8715..ded9ec6a99bb 100644 --- a/gradle/plugins/common/build.gradle.kts +++ b/gradle/plugins/common/build.gradle.kts @@ -11,9 +11,11 @@ dependencies { implementation(libs.plugins.bnd.markerCoordinates) implementation(libs.plugins.commonCustomUserData.markerCoordinates) implementation(libs.plugins.develocity.markerCoordinates) + implementation(libs.plugins.errorProne.markerCoordinates) implementation(libs.plugins.foojayResolver.markerCoordinates) implementation(libs.plugins.jmh.markerCoordinates) implementation(libs.plugins.jreleaser.markerCoordinates) + implementation(libs.plugins.nullaway.markerCoordinates) implementation(libs.plugins.openrewrite.markerCoordinates) implementation(libs.plugins.shadow.markerCoordinates) implementation(libs.plugins.spotless.markerCoordinates) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts index d8d6c70a810a..63bf6e8eb36b 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts @@ -1,5 +1,5 @@ + import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import junitbuild.extensions.javaModuleName import junitbuild.extensions.isSnapshot import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.Library @@ -208,22 +208,16 @@ tasks.withType().configureEach { tasks.withType().configureEach { options.encoding = "UTF-8" -} - -tasks.compileJava { options.compilerArgs.addAll(listOf( - "-Xlint:all", // Enables all recommended warnings. - "-Werror", // Terminates compilation when warnings occur. - "-parameters", // Generates metadata for reflection on method parameters. - "--module-version", "${project.version}" + "-Xlint:all", // Enables all recommended warnings. + "-Werror", // Terminates compilation when warnings occur. + "-parameters", // Generates metadata for reflection on method parameters. )) } -tasks.compileTestJava { +tasks.compileJava { options.compilerArgs.addAll(listOf( - "-Xlint:all", // Enables all recommended warnings. - "-Werror", // Terminates compilation when warnings occur. - "-parameters" // Generates metadata for reflection on method parameters. + "--module-version", "${project.version}" )) } 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 new file mode 100644 index 000000000000..75f0c6e70ab5 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -0,0 +1,42 @@ +import junitbuild.extensions.dependencyFromLibs +import net.ltgt.gradle.errorprone.errorprone +import net.ltgt.gradle.nullaway.nullaway + +plugins { + `java-library` + id("net.ltgt.errorprone") + id("net.ltgt.nullaway") +} + +dependencies { + errorprone(dependencyFromLibs("errorProne-core")) + errorprone(dependencyFromLibs("nullaway")) +} + +nullaway { + onlyNullMarked = true +} + +tasks.withType().configureEach { + options.errorprone { + disableAllChecks = true + nullaway { + enable() + } + } +} + +tasks.withType().named { it.startsWith("compileTest") }.configureEach { + options.errorprone.nullaway { + handleTestAssertionLibraries = true + excludedFieldAnnotations.addAll( + "org.junit.jupiter.api.io.TempDir", + "org.junit.jupiter.params.Parameter", + "org.junit.runners.Parameterized.Parameter", + "org.mockito.Captor", + "org.mockito.InjectMocks", + "org.mockito.Mock", + "org.mockito.Spy", + ) + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts index 03514d7e967a..d5f47a50e2e8 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts @@ -5,8 +5,6 @@ plugins { `java-library` } -val importAPIGuardian = "org.apiguardian.*;resolution:=\"optional\"" - val projectDescription = objects.property().convention(provider { project.description }) // This task enhances `jar` and `shadowJar` tasks with the bnd @@ -15,7 +13,9 @@ val projectDescription = objects.property().convention(provider { projec tasks.withType().named { it == "jar" || it == "shadowJar" }.all { // configure tasks eagerly as workaround for https://github.com/bndtools/bnd/issues/5695 - extra["importAPIGuardian"] = importAPIGuardian + + val importAPIGuardian by extra { "org.apiguardian.*;resolution:=\"optional\"" } + val importJSpecify by extra { "org.jspecify.*;resolution:=\"optional\"" } extensions.create(BundleTaskExtension.NAME, this).apply { properties.set(projectDescription.map { @@ -37,6 +37,7 @@ tasks.withType().named { # These are the general rules for package imports. Import-Package: \ ${importAPIGuardian},\ + ${importJSpecify},\ org.junit.platform.commons.logging;status=INTERNAL,\ kotlin.*;resolution:="optional",\ * @@ -45,6 +46,7 @@ tasks.withType().named { # the kotlin and apiguardian packages, but enough modules do to make it a default. -fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore -fixupmessages.apiguardian.import: "Unused Import-Package instructions: \\[org.apiguardian.*\\]";is:=ignore + -fixupmessages.jspecify.import: "Unused Import-Package instructions: \\[org.jspecify.*\\]";is:=ignore # This tells bnd to ignore classes it finds in `META-INF/versions/` # because bnd doesn't yet support multi-release jars. diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 402b5323eb57..f2592212772c 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.kotlin-library-conventions") + id("junitbuild.java-nullability-conventions") id("junitbuild.code-generator") `java-test-fixtures` } @@ -12,6 +13,7 @@ dependencies { api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) compileOnly(kotlin("stdlib")) diff --git a/junit-jupiter-api/src/main/java/module-info.java b/junit-jupiter-api/src/main/java/module-info.java index afb8f4cd5e45..2660f24f0d87 100644 --- a/junit-jupiter-api/src/main/java/module-info.java +++ b/junit-jupiter-api/src/main/java/module-info.java @@ -14,7 +14,10 @@ * @since 5.0 */ module org.junit.jupiter.api { + requires static transitive org.apiguardian.api; + requires static org.jspecify; + requires transitive org.junit.platform.commons; requires transitive org.opentest4j; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java index ae5f2828c795..666b7bb75dcc 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java @@ -17,6 +17,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; @@ -38,7 +39,7 @@ static void assertAll(Executable... executables) { assertAll(null, executables); } - static void assertAll(String heading, Executable... executables) { + static void assertAll(@Nullable String heading, Executable... executables) { Preconditions.notEmpty(executables, "executables array must not be null or empty"); Preconditions.containsNoNullElements(executables, "individual executables must not be null"); assertAll(heading, Arrays.stream(executables)); @@ -48,7 +49,7 @@ static void assertAll(Collection executables) { assertAll(null, executables); } - static void assertAll(String heading, Collection executables) { + static void assertAll(@Nullable String heading, Collection executables) { Preconditions.notNull(executables, "executables collection must not be null"); Preconditions.containsNoNullElements(executables, "individual executables must not be null"); assertAll(heading, executables.stream()); @@ -58,7 +59,7 @@ static void assertAll(Stream executables) { assertAll(null, executables); } - static void assertAll(String heading, Stream executables) { + static void assertAll(@Nullable String heading, Stream executables) { Preconditions.notNull(executables, "executables stream must not be null"); List failures = executables // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java index 883b7bd841cf..35bc69437fae 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java @@ -19,6 +19,9 @@ import java.util.Objects; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; +import org.opentest4j.AssertionFailedError; + /** * {@code AssertArrayEquals} is a collection of utility methods that support asserting * array equality in tests. @@ -31,145 +34,165 @@ private AssertArrayEquals() { /* no-op */ } - static void assertArrayEquals(boolean[] expected, boolean[] actual) { + static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(boolean[] expected, boolean[] actual, String message) { + static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, + @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(boolean[] expected, boolean[] actual, Supplier messageSupplier) { + static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(char[] expected, char[] actual, String message) { + static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(char[] expected, char[] actual) { + static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(char[] expected, char[] actual, Supplier messageSupplier) { + static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, + @Nullable Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(byte[] expected, byte[] actual) { + static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(byte[] expected, byte[] actual, String message) { + static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(byte[] expected, byte[] actual, Supplier messageSupplier) { + static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(short[] expected, short[] actual) { + static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(short[] expected, short[] actual, String message) { + static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(short[] expected, short[] actual, Supplier messageSupplier) { + static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(int[] expected, int[] actual) { + static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(int[] expected, int[] actual, String message) { + static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(int[] expected, int[] actual, Supplier messageSupplier) { + static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(long[] expected, long[] actual) { + static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(long[] expected, long[] actual, String message) { + static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(long[] expected, long[] actual, Supplier messageSupplier) { + static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(float[] expected, float[] actual) { + static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(float[] expected, float[] actual, String message) { + static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(float[] expected, float[] actual, Supplier messageSupplier) { + static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(float[] expected, float[] actual, float delta) { + static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta) { assertArrayEquals(expected, actual, delta, (String) null); } - static void assertArrayEquals(float[] expected, float[] actual, float delta, String message) { + static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, + @Nullable String message) { assertArrayEquals(expected, actual, delta, null, message); } - static void assertArrayEquals(float[] expected, float[] actual, float delta, Supplier messageSupplier) { + static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, delta, null, messageSupplier); } - static void assertArrayEquals(double[] expected, double[] actual) { + static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(double[] expected, double[] actual, String message) { + static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, @Nullable String message) { assertArrayEquals(expected, actual, null, message); } - static void assertArrayEquals(double[] expected, double[] actual, Supplier messageSupplier) { + static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, null, messageSupplier); } - static void assertArrayEquals(double[] expected, double[] actual, double delta) { + static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta) { assertArrayEquals(expected, actual, delta, (String) null); } - static void assertArrayEquals(double[] expected, double[] actual, double delta, String message) { + static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, + @Nullable String message) { assertArrayEquals(expected, actual, delta, null, message); } - static void assertArrayEquals(double[] expected, double[] actual, double delta, Supplier messageSupplier) { + static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, delta, null, messageSupplier); } - static void assertArrayEquals(Object[] expected, Object[] actual) { + static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual) { assertArrayEquals(expected, actual, (String) null); } - static void assertArrayEquals(Object[] expected, Object[] actual, String message) { + static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, + @Nullable String message) { assertArrayEquals(expected, actual, new ArrayDeque<>(), message); } - static void assertArrayEquals(Object[] expected, Object[] actual, Supplier messageSupplier) { + static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { assertArrayEquals(expected, actual, new ArrayDeque<>(), messageSupplier); } - private static void assertArrayEquals(boolean[] expected, boolean[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -179,13 +202,19 @@ private static void assertArrayEquals(boolean[] expected, boolean[] actual, Dequ } } - private static void assertArrayEquals(char[] expected, char[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -195,13 +224,19 @@ private static void assertArrayEquals(char[] expected, char[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -211,13 +246,19 @@ private static void assertArrayEquals(byte[] expected, byte[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -227,13 +268,19 @@ private static void assertArrayEquals(short[] expected, short[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -243,13 +290,19 @@ private static void assertArrayEquals(int[] expected, int[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -259,13 +312,19 @@ private static void assertArrayEquals(long[] expected, long[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -275,14 +334,20 @@ private static void assertArrayEquals(float[] expected, float[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { AssertionUtils.assertValidDelta(delta); if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -292,13 +357,19 @@ private static void assertArrayEquals(float[] expected, float[] actual, float de } } - private static void assertArrayEquals(double[] expected, double[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -308,14 +379,20 @@ private static void assertArrayEquals(double[] expected, double[] actual, Deque< } } - private static void assertArrayEquals(double[] expected, double[] actual, double delta, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { AssertionUtils.assertValidDelta(delta); if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -325,13 +402,19 @@ private static void assertArrayEquals(double[] expected, double[] actual, double } } - private static void assertArrayEquals(Object[] expected, Object[] actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, + Deque indexes, @Nullable Object messageOrSupplier) { if (expected == actual) { return; } - assertArraysNotNull(expected, actual, indexes, messageOrSupplier); + + if (expected == null) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { @@ -348,8 +431,8 @@ private static void assertArrayEquals(Object[] expected, Object[] actual, Deque< } } - private static void assertArrayElementsEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArrayElementsEqual(@Nullable Object expected, @Nullable Object actual, + Deque indexes, @Nullable Object messageOrSupplier) { if (expected instanceof Object[] expectedArray && actual instanceof Object[] actualArray) { assertArrayEquals(expectedArray, actualArray, indexes, messageOrSupplier); @@ -391,33 +474,32 @@ else if (isArray(expected) && actual == null) { } } - private static void assertArraysNotNull(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { - - if (expected == null) { - failExpectedArrayIsNull(indexes, messageOrSupplier); - } - if (actual == null) { - failActualArrayIsNull(indexes, messageOrSupplier); - } + private static void failExpectedArrayIsNull(@Nullable Deque indexes, @Nullable Object messageOrSupplier) { + throw expectedArrayIsNullFailure(indexes, messageOrSupplier); } - private static void failExpectedArrayIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // + private static AssertionFailedError expectedArrayIsNullFailure(@Nullable Deque indexes, + @Nullable Object messageOrSupplier) { + return assertionFailure() // .message(messageOrSupplier) // .reason("expected array was " + formatIndexes(indexes)) // - .buildAndThrow(); + .build(); } - private static void failActualArrayIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // + private static void failActualArrayIsNull(@Nullable Deque indexes, @Nullable Object messageOrSupplier) { + throw actualArrayIsNullFailure(indexes, messageOrSupplier); + } + + private static AssertionFailedError actualArrayIsNullFailure(@Nullable Deque indexes, + @Nullable Object messageOrSupplier) { + return assertionFailure() // .message(messageOrSupplier) // .reason("actual array was " + formatIndexes(indexes)) // - .buildAndThrow(); + .build(); } - private static void assertArraysHaveSameLength(int expected, int actual, Deque indexes, - Object messageOrSupplier) { + private static void assertArraysHaveSameLength(int expected, int actual, @Nullable Deque indexes, + @Nullable Object messageOrSupplier) { if (expected != actual) { assertionFailure() // @@ -429,8 +511,8 @@ private static void assertArraysHaveSameLength(int expected, int actual, Deque indexes, - Object messageOrSupplier) { + private static void failArraysNotEqual(@Nullable Object expected, @Nullable Object actual, + @Nullable Deque indexes, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // @@ -440,7 +522,7 @@ private static void failArraysNotEqual(Object expected, Object actual, Deque nullSafeIndexes(Deque indexes, int newIndex) { + private static Deque nullSafeIndexes(@Nullable Deque indexes, int newIndex) { Deque result = (indexes != null ? indexes : new ArrayDeque<>()); result.addLast(newIndex); return result; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java index 439e9795d780..82e15c85a41a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.util.StringUtils; @@ -38,15 +39,15 @@ static void assertDoesNotThrow(Executable executable) { assertDoesNotThrow(executable, (Object) null); } - static void assertDoesNotThrow(Executable executable, String message) { + static void assertDoesNotThrow(Executable executable, @Nullable String message) { assertDoesNotThrow(executable, (Object) message); } - static void assertDoesNotThrow(Executable executable, Supplier messageSupplier) { + static void assertDoesNotThrow(Executable executable, Supplier<@Nullable String> messageSupplier) { assertDoesNotThrow(executable, (Object) messageSupplier); } - private static void assertDoesNotThrow(Executable executable, Object messageOrSupplier) { + private static void assertDoesNotThrow(Executable executable, @Nullable Object messageOrSupplier) { try { executable.execute(); } @@ -56,19 +57,21 @@ private static void assertDoesNotThrow(Executable executable, Object messageOrSu } } - static T assertDoesNotThrow(ThrowingSupplier supplier) { + static T assertDoesNotThrow(ThrowingSupplier supplier) { return assertDoesNotThrow(supplier, (Object) null); } - static T assertDoesNotThrow(ThrowingSupplier supplier, String message) { + static T assertDoesNotThrow(ThrowingSupplier supplier, @Nullable String message) { return assertDoesNotThrow(supplier, (Object) message); } - static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier messageSupplier) { + static T assertDoesNotThrow(ThrowingSupplier supplier, + Supplier<@Nullable String> messageSupplier) { return assertDoesNotThrow(supplier, (Object) messageSupplier); } - private static T assertDoesNotThrow(ThrowingSupplier supplier, Object messageOrSupplier) { + private static T assertDoesNotThrow(ThrowingSupplier supplier, + @Nullable Object messageOrSupplier) { try { return supplier.get(); } @@ -79,7 +82,7 @@ private static T assertDoesNotThrow(ThrowingSupplier supplier, Object mes } @API(status = INTERNAL, since = "6.0") - public static AssertionFailedError createAssertionFailedError(Object messageOrSupplier, Throwable t) { + public static AssertionFailedError createAssertionFailedError(@Nullable Object messageOrSupplier, Throwable t) { return assertionFailure() // .message(messageOrSupplier) // .reason("Unexpected exception thrown: " + t.getClass().getName() + buildSuffix(t.getMessage())) // @@ -87,7 +90,7 @@ public static AssertionFailedError createAssertionFailedError(Object messageOrSu .build(); } - private static String buildSuffix(String message) { + private static String buildSuffix(@Nullable String message) { return StringUtils.isNotBlank(message) ? ": " + message : ""; } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java index 94afc3fe49c7..2e38bdb6f1c6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java @@ -17,6 +17,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertEquals} is a collection of utility methods that support asserting * equality on objects and primitives in tests. @@ -33,13 +35,13 @@ static void assertEquals(byte expected, byte actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(byte expected, byte actual, String message) { + static void assertEquals(byte expected, byte actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } - static void assertEquals(byte expected, byte actual, Supplier messageSupplier) { + static void assertEquals(byte expected, byte actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } @@ -49,13 +51,13 @@ static void assertEquals(char expected, char actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(char expected, char actual, String message) { + static void assertEquals(char expected, char actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } - static void assertEquals(char expected, char actual, Supplier messageSupplier) { + static void assertEquals(char expected, char actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } @@ -65,13 +67,13 @@ static void assertEquals(double expected, double actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(double expected, double actual, String message) { + static void assertEquals(double expected, double actual, @Nullable String message) { if (!doublesAreEqual(expected, actual)) { failNotEqual(expected, actual, message); } } - static void assertEquals(double expected, double actual, Supplier messageSupplier) { + static void assertEquals(double expected, double actual, Supplier<@Nullable String> messageSupplier) { if (!doublesAreEqual(expected, actual)) { failNotEqual(expected, actual, messageSupplier); } @@ -81,13 +83,13 @@ static void assertEquals(double expected, double actual, double delta) { assertEquals(expected, actual, delta, (String) null); } - static void assertEquals(double expected, double actual, double delta, String message) { + static void assertEquals(double expected, double actual, double delta, @Nullable String message) { if (!doublesAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, message); } } - static void assertEquals(double expected, double actual, double delta, Supplier messageSupplier) { + static void assertEquals(double expected, double actual, double delta, Supplier<@Nullable String> messageSupplier) { if (!doublesAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, messageSupplier); } @@ -97,13 +99,13 @@ static void assertEquals(float expected, float actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(float expected, float actual, String message) { + static void assertEquals(float expected, float actual, @Nullable String message) { if (!floatsAreEqual(expected, actual)) { failNotEqual(expected, actual, message); } } - static void assertEquals(float expected, float actual, Supplier messageSupplier) { + static void assertEquals(float expected, float actual, Supplier<@Nullable String> messageSupplier) { if (!floatsAreEqual(expected, actual)) { failNotEqual(expected, actual, messageSupplier); } @@ -113,13 +115,13 @@ static void assertEquals(float expected, float actual, float delta) { assertEquals(expected, actual, delta, (String) null); } - static void assertEquals(float expected, float actual, float delta, String message) { + static void assertEquals(float expected, float actual, float delta, @Nullable String message) { if (!floatsAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, message); } } - static void assertEquals(float expected, float actual, float delta, Supplier messageSupplier) { + static void assertEquals(float expected, float actual, float delta, Supplier<@Nullable String> messageSupplier) { if (!floatsAreEqual(expected, actual, delta)) { failNotEqual(expected, actual, messageSupplier); } @@ -129,13 +131,13 @@ static void assertEquals(short expected, short actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(short expected, short actual, String message) { + static void assertEquals(short expected, short actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } - static void assertEquals(short expected, short actual, Supplier messageSupplier) { + static void assertEquals(short expected, short actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } @@ -145,13 +147,13 @@ static void assertEquals(int expected, int actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(int expected, int actual, String message) { + static void assertEquals(int expected, int actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } - static void assertEquals(int expected, int actual, Supplier messageSupplier) { + static void assertEquals(int expected, int actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } @@ -161,35 +163,37 @@ static void assertEquals(long expected, long actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(long expected, long actual, String message) { + static void assertEquals(long expected, long actual, @Nullable String message) { if (expected != actual) { failNotEqual(expected, actual, message); } } - static void assertEquals(long expected, long actual, Supplier messageSupplier) { + static void assertEquals(long expected, long actual, Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotEqual(expected, actual, messageSupplier); } } - static void assertEquals(Object expected, Object actual) { + static void assertEquals(@Nullable Object expected, @Nullable Object actual) { assertEquals(expected, actual, (String) null); } - static void assertEquals(Object expected, Object actual, String message) { + static void assertEquals(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { if (!objectsAreEqual(expected, actual)) { failNotEqual(expected, actual, message); } } - static void assertEquals(Object expected, Object actual, Supplier messageSupplier) { + static void assertEquals(@Nullable Object expected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { if (!objectsAreEqual(expected, actual)) { failNotEqual(expected, actual, messageSupplier); } } - private static void failNotEqual(Object expected, Object actual, Object messageOrSupplier) { + private static void failNotEqual(@Nullable Object expected, @Nullable Object actual, + @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(expected) // 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 f7582574166d..ea248480a299 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 @@ -15,6 +15,8 @@ import java.util.function.BooleanSupplier; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertFalse} is a collection of utility methods that support asserting * {@code false} in tests. @@ -31,13 +33,13 @@ static void assertFalse(boolean condition) { assertFalse(condition, (String) null); } - static void assertFalse(boolean condition, String message) { + static void assertFalse(boolean condition, @Nullable String message) { if (condition) { failNotFalse(message); } } - static void assertFalse(boolean condition, Supplier messageSupplier) { + static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { if (condition) { failNotFalse(messageSupplier); } @@ -47,15 +49,15 @@ static void assertFalse(BooleanSupplier booleanSupplier) { assertFalse(booleanSupplier.getAsBoolean(), (String) null); } - static void assertFalse(BooleanSupplier booleanSupplier, String message) { + static void assertFalse(BooleanSupplier booleanSupplier, @Nullable String message) { assertFalse(booleanSupplier.getAsBoolean(), message); } - static void assertFalse(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + static void assertFalse(BooleanSupplier booleanSupplier, Supplier<@Nullable String> messageSupplier) { assertFalse(booleanSupplier.getAsBoolean(), messageSupplier); } - private static void failNotFalse(Object messageOrSupplier) { + private static void failNotFalse(@Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(false) // 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 4d3197ba46fe..02f72aeffdc3 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 @@ -14,6 +14,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertInstanceOf} is a collection of utility methods that support * asserting that an object is of an expected type — in other words, if it @@ -27,19 +29,22 @@ private AssertInstanceOf() { /* no-op */ } - static T assertInstanceOf(Class expectedType, Object actualValue) { + static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return assertInstanceOf(expectedType, actualValue, (Object) null); } - static T assertInstanceOf(Class expectedType, Object actualValue, String message) { + static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return assertInstanceOf(expectedType, actualValue, (Object) message); } - static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, + Supplier<@Nullable String> messageSupplier) { return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); } - private static T assertInstanceOf(Class expectedType, Object actualValue, Object messageOrSupplier) { + @SuppressWarnings("NullAway") + private static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, + @Nullable Object messageOrSupplier) { if (!expectedType.isInstance(actualValue)) { assertionFailure() // .message(messageOrSupplier) // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java index effc99618785..6512d10e8f12 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java @@ -22,6 +22,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; +import org.opentest4j.AssertionFailedError; + /** * {@code AssertIterable} is a collection of utility methods that support asserting * Iterable equality in tests. @@ -34,30 +37,37 @@ private AssertIterableEquals() { /* no-op */ } - static void assertIterableEquals(Iterable expected, Iterable actual) { + static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual) { assertIterableEquals(expected, actual, (String) null); } - static void assertIterableEquals(Iterable expected, Iterable actual, String message) { + static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, + @Nullable String message) { assertIterableEquals(expected, actual, new ArrayDeque<>(), message); } - static void assertIterableEquals(Iterable expected, Iterable actual, Supplier messageSupplier) { + static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, + Supplier<@Nullable String> messageSupplier) { assertIterableEquals(expected, actual, new ArrayDeque<>(), messageSupplier); } - private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, - Object messageOrSupplier) { + private static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, + Deque indexes, @Nullable Object messageOrSupplier) { assertIterableEquals(expected, actual, indexes, messageOrSupplier, new LinkedHashMap<>()); } - private static void assertIterableEquals(Iterable expected, Iterable actual, Deque indexes, - Object messageOrSupplier, Map investigatedElements) { + private static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, + Deque indexes, @Nullable Object messageOrSupplier, Map investigatedElements) { if (expected == actual) { return; } - assertIterablesNotNull(expected, actual, indexes, messageOrSupplier); + if (expected == null) { + throw expectedIterableIsNullFailure(indexes, messageOrSupplier); + } + if (actual == null) { + throw actualIterableIsNullFailure(indexes, messageOrSupplier); + } Iterator expectedIterator = expected.iterator(); Iterator actualIterator = actual.iterator(); @@ -80,7 +90,7 @@ private static void assertIterableEquals(Iterable expected, Iterable actua } private static void assertIterableElementsEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier, Map investigatedElements) { + @Nullable Object messageOrSupplier, Map investigatedElements) { // If both are equal, we don't need to check recursively. if (Objects.equals(expected, actual)) { @@ -123,8 +133,8 @@ private static void assertIterableElementsEqual(Object expected, Object actual, } } - private static void assertIterablesNotNull(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { + private static void assertIterablesNotNull(@Nullable Object expected, @Nullable Object actual, + Deque indexes, @Nullable Object messageOrSupplier) { if (expected == null) { failExpectedIterableIsNull(indexes, messageOrSupplier); @@ -134,22 +144,32 @@ private static void assertIterablesNotNull(Object expected, Object actual, Deque } } - private static void failExpectedIterableIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // + private static void failExpectedIterableIsNull(Deque indexes, @Nullable Object messageOrSupplier) { + throw expectedIterableIsNullFailure(indexes, messageOrSupplier); + } + + private static AssertionFailedError expectedIterableIsNullFailure(Deque indexes, + @Nullable Object messageOrSupplier) { + return assertionFailure() // .message(messageOrSupplier) // .reason("expected iterable was " + formatIndexes(indexes)) // - .buildAndThrow(); + .build(); } - private static void failActualIterableIsNull(Deque indexes, Object messageOrSupplier) { - assertionFailure() // + private static void failActualIterableIsNull(Deque indexes, @Nullable Object messageOrSupplier) { + throw actualIterableIsNullFailure(indexes, messageOrSupplier); + } + + private static AssertionFailedError actualIterableIsNullFailure(Deque indexes, + @Nullable Object messageOrSupplier) { + return assertionFailure() // .message(messageOrSupplier) // .reason("actual iterable was " + formatIndexes(indexes)) // - .buildAndThrow(); + .build(); } private static void assertIteratorsAreEmpty(Iterator expected, Iterator actual, int processed, - Deque indexes, Object messageOrSupplier) { + Deque indexes, @Nullable Object messageOrSupplier) { if (expected.hasNext() || actual.hasNext()) { AtomicInteger expectedCount = new AtomicInteger(processed); @@ -168,7 +188,7 @@ private static void assertIteratorsAreEmpty(Iterator expected, Iterator ac } private static void failIterablesNotEqual(Object expected, Object actual, Deque indexes, - Object messageOrSupplier) { + @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java index 65a99466ac69..2f61fe53e8e1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java @@ -23,6 +23,8 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + /** * {@code AssertLinesMatch} is a collection of utility methods that support asserting * lines of {@link String} equality or {@link java.util.regex.Pattern}-match in tests. @@ -41,7 +43,7 @@ static void assertLinesMatch(List expectedLines, List actualLine assertLinesMatch(expectedLines, actualLines, (Object) null); } - static void assertLinesMatch(List expectedLines, List actualLines, String message) { + static void assertLinesMatch(List expectedLines, List actualLines, @Nullable String message) { assertLinesMatch(expectedLines, actualLines, (Object) message); } @@ -49,11 +51,12 @@ static void assertLinesMatch(Stream expectedLines, Stream actual assertLinesMatch(expectedLines, actualLines, (Object) null); } - static void assertLinesMatch(Stream expectedLines, Stream actualLines, String message) { + static void assertLinesMatch(Stream expectedLines, Stream actualLines, @Nullable String message) { assertLinesMatch(expectedLines, actualLines, (Object) message); } - static void assertLinesMatch(Stream expectedLines, Stream actualLines, Object messageOrSupplier) { + static void assertLinesMatch(Stream expectedLines, Stream actualLines, + @Nullable Object messageOrSupplier) { notNull(expectedLines, "expectedLines must not be null"); notNull(actualLines, "actualLines must not be null"); @@ -67,7 +70,8 @@ static void assertLinesMatch(Stream expectedLines, Stream actual assertLinesMatch(expectedListOfStrings, actualListOfStrings, messageOrSupplier); } - static void assertLinesMatch(List expectedLines, List actualLines, Object messageOrSupplier) { + static void assertLinesMatch(List expectedLines, List actualLines, + @Nullable Object messageOrSupplier) { notNull(expectedLines, "expectedLines must not be null"); notNull(actualLines, "actualLines must not be null"); @@ -83,9 +87,11 @@ private static class LinesMatcher { private final List expectedLines; private final List actualLines; + + @Nullable private final Object messageOrSupplier; - LinesMatcher(List expectedLines, List actualLines, Object messageOrSupplier) { + LinesMatcher(List expectedLines, List actualLines, @Nullable Object messageOrSupplier) { this.expectedLines = expectedLines; this.actualLines = actualLines; this.messageOrSupplier = messageOrSupplier; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java index 1ccc41b7bb13..e78a32eb4aa8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java @@ -17,6 +17,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertNotEquals} is a collection of utility methods that support asserting * inequality in objects and primitive values in tests. @@ -39,7 +41,7 @@ static void assertNotEquals(byte unexpected, byte actual) { /** * @since 5.4 */ - static void assertNotEquals(byte unexpected, byte actual, String message) { + static void assertNotEquals(byte unexpected, byte actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } @@ -48,7 +50,7 @@ static void assertNotEquals(byte unexpected, byte actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { + static void assertNotEquals(byte unexpected, byte actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } @@ -64,7 +66,7 @@ static void assertNotEquals(short unexpected, short actual) { /** * @since 5.4 */ - static void assertNotEquals(short unexpected, short actual, String message) { + static void assertNotEquals(short unexpected, short actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } @@ -73,7 +75,7 @@ static void assertNotEquals(short unexpected, short actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { + static void assertNotEquals(short unexpected, short actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } @@ -89,7 +91,7 @@ static void assertNotEquals(int unexpected, int actual) { /** * @since 5.4 */ - static void assertNotEquals(int unexpected, int actual, String message) { + static void assertNotEquals(int unexpected, int actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } @@ -98,7 +100,7 @@ static void assertNotEquals(int unexpected, int actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { + static void assertNotEquals(int unexpected, int actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } @@ -114,7 +116,7 @@ static void assertNotEquals(long unexpected, long actual) { /** * @since 5.4 */ - static void assertNotEquals(long unexpected, long actual, String message) { + static void assertNotEquals(long unexpected, long actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } @@ -123,7 +125,7 @@ static void assertNotEquals(long unexpected, long actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { + static void assertNotEquals(long unexpected, long actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } @@ -139,7 +141,7 @@ static void assertNotEquals(float unexpected, float actual) { /** * @since 5.4 */ - static void assertNotEquals(float unexpected, float actual, String message) { + static void assertNotEquals(float unexpected, float actual, @Nullable String message) { if (floatsAreEqual(unexpected, actual)) { failEqual(actual, message); } @@ -148,7 +150,7 @@ static void assertNotEquals(float unexpected, float actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { + static void assertNotEquals(float unexpected, float actual, Supplier<@Nullable String> messageSupplier) { if (floatsAreEqual(unexpected, actual)) { failEqual(actual, messageSupplier); } @@ -164,7 +166,7 @@ static void assertNotEquals(float unexpected, float actual, float delta) { /** * @since 5.4 */ - static void assertNotEquals(float unexpected, float actual, float delta, String message) { + static void assertNotEquals(float unexpected, float actual, float delta, @Nullable String message) { if (floatsAreEqual(unexpected, actual, delta)) { failEqual(actual, message); } @@ -173,7 +175,8 @@ static void assertNotEquals(float unexpected, float actual, float delta, String /** * @since 5.4 */ - static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { + static void assertNotEquals(float unexpected, float actual, float delta, + Supplier<@Nullable String> messageSupplier) { if (floatsAreEqual(unexpected, actual, delta)) { failEqual(actual, messageSupplier); } @@ -189,7 +192,7 @@ static void assertNotEquals(double unexpected, double actual) { /** * @since 5.4 */ - static void assertNotEquals(double unexpected, double actual, String message) { + static void assertNotEquals(double unexpected, double actual, @Nullable String message) { if (doublesAreEqual(unexpected, actual)) { failEqual(actual, message); } @@ -198,7 +201,7 @@ static void assertNotEquals(double unexpected, double actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { + static void assertNotEquals(double unexpected, double actual, Supplier<@Nullable String> messageSupplier) { if (doublesAreEqual(unexpected, actual)) { failEqual(actual, messageSupplier); } @@ -214,7 +217,7 @@ static void assertNotEquals(double unexpected, double actual, double delta) { /** * @since 5.4 */ - static void assertNotEquals(double unexpected, double actual, double delta, String message) { + static void assertNotEquals(double unexpected, double actual, double delta, @Nullable String message) { if (doublesAreEqual(unexpected, actual, delta)) { failEqual(actual, message); } @@ -223,7 +226,8 @@ static void assertNotEquals(double unexpected, double actual, double delta, Stri /** * @since 5.4 */ - static void assertNotEquals(double unexpected, double actual, double delta, Supplier messageSupplier) { + static void assertNotEquals(double unexpected, double actual, double delta, + Supplier<@Nullable String> messageSupplier) { if (doublesAreEqual(unexpected, actual, delta)) { failEqual(actual, messageSupplier); } @@ -239,7 +243,7 @@ static void assertNotEquals(char unexpected, char actual) { /** * @since 5.4 */ - static void assertNotEquals(char unexpected, char actual, String message) { + static void assertNotEquals(char unexpected, char actual, @Nullable String message) { if (unexpected == actual) { failEqual(actual, message); } @@ -248,29 +252,30 @@ static void assertNotEquals(char unexpected, char actual, String message) { /** * @since 5.4 */ - static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { + static void assertNotEquals(char unexpected, char actual, Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failEqual(actual, messageSupplier); } } - static void assertNotEquals(Object unexpected, Object actual) { + static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual) { assertNotEquals(unexpected, actual, (String) null); } - static void assertNotEquals(Object unexpected, Object actual, String message) { + static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { if (objectsAreEqual(unexpected, actual)) { failEqual(actual, message); } } - static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { + static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { if (objectsAreEqual(unexpected, actual)) { failEqual(actual, messageSupplier); } } - private static void failEqual(Object actual, Object messageOrSupplier) { + private static void failEqual(@Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("expected: not equal but was: <" + actual + ">") // 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 f5fb9b849230..1f1208f297dc 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 @@ -14,6 +14,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertNotNull} is a collection of utility methods that support asserting * that there is an object. @@ -26,23 +28,23 @@ private AssertNotNull() { /* no-op */ } - static void assertNotNull(Object actual) { + static void assertNotNull(@Nullable Object actual) { assertNotNull(actual, (String) null); } - static void assertNotNull(Object actual, String message) { + static void assertNotNull(@Nullable Object actual, @Nullable String message) { if (actual == null) { failNull(message); } } - static void assertNotNull(Object actual, Supplier messageSupplier) { + static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual == null) { failNull(messageSupplier); } } - private static void failNull(Object messageOrSupplier) { + private static void failNull(@Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("expected: not ") // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java index 860371937076..d46c8f711fe1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java @@ -14,6 +14,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertNotSame} is a collection of utility methods that support asserting * two objects are not the same. @@ -26,23 +28,24 @@ private AssertNotSame() { /* no-op */ } - static void assertNotSame(Object unexpected, Object actual) { + static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual) { assertNotSame(unexpected, actual, (String) null); } - static void assertNotSame(Object unexpected, Object actual, String message) { + static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { if (unexpected == actual) { failSame(actual, message); } } - static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { + static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { if (unexpected == actual) { failSame(actual, messageSupplier); } } - private static void failSame(Object actual, Object messageOrSupplier) { + private static void failSame(@Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .reason("expected: not same but was: <" + actual + ">") // 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 a81e7925415f..1acf4c1e6154 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 @@ -14,6 +14,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertNull} is a collection of utility methods that support asserting * there is no object. @@ -26,23 +28,23 @@ private AssertNull() { /* no-op */ } - static void assertNull(Object actual) { + static void assertNull(@Nullable Object actual) { assertNull(actual, (String) null); } - static void assertNull(Object actual, String message) { + static void assertNull(@Nullable Object actual, @Nullable String message) { if (actual != null) { failNotNull(actual, message); } } - static void assertNull(Object actual, Supplier messageSupplier) { + static void assertNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual != null) { failNotNull(actual, messageSupplier); } } - private static void failNotNull(Object actual, Object messageOrSupplier) { + private static void failNotNull(@Nullable Object actual, @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(null) // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java index f006dab823ff..2cf1476f3742 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java @@ -14,6 +14,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertSame} is a collection of utility methods that support asserting * two objects are the same. @@ -26,23 +28,25 @@ private AssertSame() { /* no-op */ } - static void assertSame(Object expected, Object actual) { + static void assertSame(@Nullable Object expected, @Nullable Object actual) { assertSame(expected, actual, (String) null); } - static void assertSame(Object expected, Object actual, String message) { + static void assertSame(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { if (expected != actual) { failNotSame(expected, actual, message); } } - static void assertSame(Object expected, Object actual, Supplier messageSupplier) { + static void assertSame(@Nullable Object expected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { if (expected != actual) { failNotSame(expected, actual, messageSupplier); } } - private static void failNotSame(Object expected, Object actual, Object messageOrSupplier) { + private static void failNotSame(@Nullable Object expected, @Nullable Object actual, + @Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(expected) // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java index f8921615aabb..d198486087a4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; @@ -34,19 +35,20 @@ static T assertThrows(Class expectedType, Executable ex return assertThrows(expectedType, executable, (Object) null); } - static T assertThrows(Class expectedType, Executable executable, String message) { + static T assertThrows(Class expectedType, Executable executable, + @Nullable String message) { return assertThrows(expectedType, executable, (Object) message); } static T assertThrows(Class expectedType, Executable executable, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { return assertThrows(expectedType, executable, (Object) messageSupplier); } @SuppressWarnings("unchecked") private static T assertThrows(Class expectedType, Executable executable, - Object messageOrSupplier) { + @Nullable Object messageOrSupplier) { try { executable.execute(); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java index 0eebe693c380..59e72305be9e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.UnrecoverableExceptions; @@ -34,19 +35,20 @@ static T assertThrowsExactly(Class expectedType, Execut return assertThrowsExactly(expectedType, executable, (Object) null); } - static T assertThrowsExactly(Class expectedType, Executable executable, String message) { + static T assertThrowsExactly(Class expectedType, Executable executable, + @Nullable String message) { return assertThrowsExactly(expectedType, executable, (Object) message); } static T assertThrowsExactly(Class expectedType, Executable executable, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { return assertThrowsExactly(expectedType, executable, (Object) messageSupplier); } @SuppressWarnings("unchecked") private static T assertThrowsExactly(Class expectedType, Executable executable, - Object messageOrSupplier) { + @Nullable Object messageOrSupplier) { try { executable.execute(); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 3aadf60f88af..0d188e166197 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -16,6 +16,7 @@ import java.time.Duration; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; @@ -35,36 +36,41 @@ static void assertTimeout(Duration timeout, Executable executable) { assertTimeout(timeout, executable, (String) null); } - static void assertTimeout(Duration timeout, Executable executable, String message) { + @SuppressWarnings("NullAway") + static void assertTimeout(Duration timeout, Executable executable, @Nullable String message) { assertTimeout(timeout, () -> { executable.execute(); return null; }, message); } - static void assertTimeout(Duration timeout, Executable executable, Supplier messageSupplier) { + @SuppressWarnings("NullAway") + static void assertTimeout(Duration timeout, Executable executable, Supplier<@Nullable String> messageSupplier) { assertTimeout(timeout, () -> { executable.execute(); return null; }, messageSupplier); } - static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { + static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { return assertTimeout(timeout, supplier, (Object) null); } - static T assertTimeout(Duration timeout, ThrowingSupplier supplier, String message) { + static T assertTimeout(Duration timeout, ThrowingSupplier supplier, + @Nullable String message) { return assertTimeout(timeout, supplier, (Object) message); } - static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Supplier messageSupplier) { + static T assertTimeout(Duration timeout, ThrowingSupplier supplier, + Supplier<@Nullable String> messageSupplier) { return assertTimeout(timeout, supplier, (Object) messageSupplier); } - private static T assertTimeout(Duration timeout, ThrowingSupplier supplier, Object messageOrSupplier) { + private static T assertTimeout(Duration timeout, ThrowingSupplier supplier, + @Nullable Object messageOrSupplier) { long timeoutInMillis = timeout.toMillis(); long start = System.currentTimeMillis(); - T result = null; + T result; try { result = supplier.get(); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java index 1eaea9ce4f3e..a2d277de1932 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java @@ -10,9 +10,11 @@ package org.junit.jupiter.api; +import static java.util.Objects.requireNonNullElse; import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; +import java.io.Serial; import java.time.Duration; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -25,6 +27,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.platform.commons.JUnitException; @@ -43,37 +46,42 @@ static void assertTimeoutPreemptively(Duration timeout, Executable executable) { assertTimeoutPreemptively(timeout, executable, (String) null); } - static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { + @SuppressWarnings("NullAway") + static void assertTimeoutPreemptively(Duration timeout, Executable executable, @Nullable String message) { assertTimeoutPreemptively(timeout, () -> { executable.execute(); return null; }, message); } - static void assertTimeoutPreemptively(Duration timeout, Executable executable, Supplier messageSupplier) { + @SuppressWarnings("NullAway") + static void assertTimeoutPreemptively(Duration timeout, Executable executable, + Supplier<@Nullable String> messageSupplier) { assertTimeoutPreemptively(timeout, () -> { executable.execute(); return null; }, messageSupplier); } - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); } - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + @Nullable String message) { return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, AssertTimeoutPreemptively::createAssertionFailure); } - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { + static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, + Supplier<@Nullable String> messageSupplier) { return assertTimeoutPreemptively(timeout, supplier, messageSupplier, AssertTimeoutPreemptively::createAssertionFailure); } - static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier, Assertions.TimeoutFailureFactory failureFactory) throws E { + static T assertTimeoutPreemptively(Duration timeout, + ThrowingSupplier supplier, @Nullable Supplier<@Nullable String> messageSupplier, + Assertions.TimeoutFailureFactory failureFactory) throws E { AtomicReference threadReference = new AtomicReference<>(); ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); @@ -87,8 +95,8 @@ static T assertTimeoutPreemptively(Duration timeout, Th } } - private static Future submitTask(ThrowingSupplier supplier, AtomicReference threadReference, - ExecutorService executorService) { + private static Future submitTask(ThrowingSupplier supplier, + AtomicReference threadReference, ExecutorService executorService) { return executorService.submit(() -> { try { threadReference.set(Thread.currentThread()); @@ -100,9 +108,10 @@ private static Future submitTask(ThrowingSupplier supplier, AtomicRefe }); } - private static T resolveFutureAndHandleException(Future future, Duration timeout, - Supplier messageSupplier, Supplier threadSupplier, - Assertions.TimeoutFailureFactory failureFactory) throws E { + private static T resolveFutureAndHandleException(Future future, + Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, + Supplier<@Nullable Thread> threadSupplier, Assertions.TimeoutFailureFactory failureFactory) + throws E, RuntimeException { try { return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); } @@ -116,15 +125,15 @@ private static T resolveFutureAndHandleException(Future throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause, thread); } catch (ExecutionException ex) { - throw throwAsUncheckedException(ex.getCause()); + throw throwAsUncheckedException(requireNonNullElse(ex.getCause(), ex)); } catch (Throwable ex) { throw throwAsUncheckedException(ex); } } - private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, - Throwable cause, Thread thread) { + private static AssertionFailedError createAssertionFailure(Duration timeout, + @Nullable Supplier<@Nullable String> messageSupplier, @Nullable Throwable cause, @Nullable Thread thread) { return assertionFailure() // .message(messageSupplier) // .reason("execution timed out after " + timeout.toMillis() + " ms") // @@ -134,6 +143,7 @@ private static AssertionFailedError createAssertionFailure(Duration timeout, Sup private static class ExecutionTimeoutException extends JUnitException { + @Serial private static final long serialVersionUID = 1L; ExecutionTimeoutException(String message) { 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 af43e0613263..c106e08995e5 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 @@ -15,6 +15,8 @@ import java.util.function.BooleanSupplier; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * {@code AssertTrue} is a collection of utility methods that support asserting * {@code true} in tests. @@ -31,13 +33,13 @@ static void assertTrue(boolean condition) { assertTrue(condition, (String) null); } - static void assertTrue(boolean condition, String message) { + static void assertTrue(boolean condition, @Nullable String message) { if (!condition) { failNotTrue(message); } } - static void assertTrue(boolean condition, Supplier messageSupplier) { + static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { if (!condition) { failNotTrue(messageSupplier); } @@ -47,15 +49,15 @@ static void assertTrue(BooleanSupplier booleanSupplier) { assertTrue(booleanSupplier.getAsBoolean(), (String) null); } - static void assertTrue(BooleanSupplier booleanSupplier, String message) { + static void assertTrue(BooleanSupplier booleanSupplier, @Nullable String message) { assertTrue(booleanSupplier.getAsBoolean(), message); } - static void assertTrue(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + static void assertTrue(BooleanSupplier booleanSupplier, Supplier<@Nullable String> messageSupplier) { assertTrue(booleanSupplier.getAsBoolean(), messageSupplier); } - private static void failNotTrue(Object messageOrSupplier) { + private static void failNotTrue(@Nullable Object messageOrSupplier) { assertionFailure() // .message(messageOrSupplier) // .expected(true) // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java index 2af178c2ee46..fdb5cdd68d52 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.opentest4j.AssertionFailedError; @@ -31,12 +32,23 @@ @API(status = STABLE, since = "5.9") public class AssertionFailureBuilder { + @Nullable private Object message; + + @Nullable private Throwable cause; + private boolean mismatch; + + @Nullable private Object expected; + + @Nullable private Object actual; + + @Nullable private String reason; + private boolean includeValuesInMessage = true; /** @@ -59,7 +71,7 @@ private AssertionFailureBuilder() { * @param message the user-defined failure message; may be {@code null} * @return this builder for method chaining */ - public AssertionFailureBuilder message(Object message) { + public AssertionFailureBuilder message(@Nullable Object message) { this.message = message; return this; } @@ -70,7 +82,7 @@ public AssertionFailureBuilder message(Object message) { * @param reason the failure reason; may be {@code null} * @return this builder for method chaining */ - public AssertionFailureBuilder reason(String reason) { + public AssertionFailureBuilder reason(@Nullable String reason) { this.reason = reason; return this; } @@ -81,7 +93,7 @@ public AssertionFailureBuilder reason(String reason) { * @param cause the failure cause; may be {@code null} * @return this builder for method chaining */ - public AssertionFailureBuilder cause(Throwable cause) { + public AssertionFailureBuilder cause(@Nullable Throwable cause) { this.cause = cause; return this; } @@ -92,7 +104,7 @@ public AssertionFailureBuilder cause(Throwable cause) { * @param expected the expected value; may be {@code null} * @return this builder for method chaining */ - public AssertionFailureBuilder expected(Object expected) { + public AssertionFailureBuilder expected(@Nullable Object expected) { this.mismatch = true; this.expected = expected; return this; @@ -104,7 +116,7 @@ public AssertionFailureBuilder expected(Object expected) { * @param actual the actual value; may be {@code null} * @return this builder for method chaining */ - public AssertionFailureBuilder actual(Object actual) { + public AssertionFailureBuilder actual(@Nullable Object actual) { this.mismatch = true; this.actual = actual; return this; @@ -152,7 +164,7 @@ public AssertionFailedError build() { : new AssertionFailedError(message, cause); } - private static String nullSafeGet(Object messageOrSupplier) { + private static @Nullable String nullSafeGet(@Nullable Object messageOrSupplier) { if (messageOrSupplier == null) { return null; } @@ -163,11 +175,11 @@ private static String nullSafeGet(Object messageOrSupplier) { return StringUtils.nullSafeToString(messageOrSupplier); } - private static String buildPrefix(String message) { + private static String buildPrefix(@Nullable String message) { return (StringUtils.isNotBlank(message) ? message + " ==> " : ""); } - private static String formatValues(Object expected, Object actual) { + private static String formatValues(@Nullable Object expected, @Nullable Object actual) { String expectedString = toString(expected); String actualString = toString(actual); if (expectedString.equals(actualString)) { @@ -177,7 +189,7 @@ private static String formatValues(Object expected, Object actual) { return "expected: <%s> but was: <%s>".formatted(expectedString, actualString); } - private static String formatClassAndValue(Object value, String valueString) { + private static String formatClassAndValue(@Nullable Object value, String valueString) { // If the value is null, return instead of null. if (value == null) { return ""; @@ -187,18 +199,18 @@ private static String formatClassAndValue(Object value, String valueString) { return (value instanceof Class ? "<" + classAndHash + ">" : classAndHash + "<" + valueString + ">"); } - private static String toString(Object obj) { + private static String toString(@Nullable Object obj) { if (obj instanceof Class clazz) { return getCanonicalName(clazz); } return StringUtils.nullSafeToString(obj); } - private static String toHash(Object obj) { + private static String toHash(@Nullable Object obj) { return (obj == null ? "" : "@" + Integer.toHexString(System.identityHashCode(obj))); } - private static String getClassName(Object obj) { + private static String getClassName(@Nullable Object obj) { return (obj == null ? "null" : obj instanceof Class clazz ? getCanonicalName(clazz) : obj.getClass().getName()); } 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 9431e30b3940..1d18b446f2f9 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 @@ -15,6 +15,7 @@ import java.util.Deque; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; @@ -34,23 +35,23 @@ static void fail() { throw new AssertionFailedError(); } - static void fail(String message) { + static void fail(@Nullable String message) { throw new AssertionFailedError(message); } - static void fail(String message, Throwable cause) { + static void fail(@Nullable String message, @Nullable Throwable cause) { throw new AssertionFailedError(message, cause); } - static void fail(Throwable cause) { + static void fail(@Nullable Throwable cause) { throw new AssertionFailedError(null, cause); } - static void fail(Supplier messageSupplier) { + static void fail(Supplier<@Nullable String> messageSupplier) { throw new AssertionFailedError(nullSafeGet(messageSupplier)); } - static String nullSafeGet(Supplier messageSupplier) { + static @Nullable String nullSafeGet(@Nullable Supplier<@Nullable String> messageSupplier) { return (messageSupplier != null ? messageSupplier.get() : null); } @@ -65,7 +66,7 @@ static String getCanonicalName(Class clazz) { } } - static String formatIndexes(Deque indexes) { + static String formatIndexes(@Nullable Deque indexes) { if (indexes == null || indexes.isEmpty()) { return ""; } @@ -103,7 +104,7 @@ static boolean doublesAreEqual(double value1, double value2) { return Double.doubleToLongBits(value1) == Double.doubleToLongBits(value2); } - static boolean objectsAreEqual(Object obj1, Object obj2) { + static boolean objectsAreEqual(@Nullable Object obj1, @Nullable Object obj2) { if (obj1 == null) { return (obj2 == null); } 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 cd037a416914..90f5837520af 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 @@ -23,6 +23,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; import org.opentest4j.MultipleFailuresError; @@ -115,6 +116,7 @@ protected Assertions() { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ + @SuppressWarnings("NullAway") public static V fail() { AssertionUtils.fail(); return null; // appeasing the compiler: this line will never be executed. @@ -134,7 +136,8 @@ public static V fail() { * Stream.of().map(entry -> fail("should not be called")); * } */ - public static V fail(String message) { + @SuppressWarnings("NullAway") + public static V fail(@Nullable String message) { AssertionUtils.fail(message); return null; // appeasing the compiler: this line will never be executed. } @@ -146,7 +149,8 @@ public static V fail(String message) { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ - public static V fail(String message, Throwable cause) { + @SuppressWarnings("NullAway") + public static V fail(@Nullable String message, @Nullable Throwable cause) { AssertionUtils.fail(message, cause); return null; // appeasing the compiler: this line will never be executed. } @@ -157,7 +161,8 @@ public static V fail(String message, Throwable cause) { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ - public static V fail(Throwable cause) { + @SuppressWarnings("NullAway") + public static V fail(@Nullable Throwable cause) { AssertionUtils.fail(cause); return null; // appeasing the compiler: this line will never be executed. } @@ -169,7 +174,8 @@ public static V fail(Throwable cause) { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ - public static V fail(Supplier messageSupplier) { + @SuppressWarnings("NullAway") + public static V fail(Supplier<@Nullable String> messageSupplier) { AssertionUtils.fail(messageSupplier); return null; // appeasing the compiler: this line will never be executed. } @@ -187,7 +193,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}. */ - public static void assertTrue(boolean condition, Supplier messageSupplier) { + public static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { AssertTrue.assertTrue(condition, messageSupplier); } @@ -202,7 +208,7 @@ public static void assertTrue(BooleanSupplier booleanSupplier) { * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. *

Fails with the supplied failure {@code message}. */ - public static void assertTrue(BooleanSupplier booleanSupplier, String message) { + public static void assertTrue(BooleanSupplier booleanSupplier, @Nullable String message) { AssertTrue.assertTrue(booleanSupplier, message); } @@ -210,7 +216,7 @@ public static void assertTrue(BooleanSupplier booleanSupplier, String message) { * Assert that the supplied {@code condition} is {@code true}. *

Fails with the supplied failure {@code message}. */ - public static void assertTrue(boolean condition, String message) { + public static void assertTrue(boolean condition, @Nullable String message) { AssertTrue.assertTrue(condition, message); } @@ -218,7 +224,7 @@ public static void assertTrue(boolean condition, String message) { * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code true}. *

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

Fails with the supplied failure {@code message}. */ - public static void assertFalse(boolean condition, String message) { + public static void assertFalse(boolean condition, @Nullable String message) { AssertFalse.assertFalse(condition, message); } @@ -243,7 +249,7 @@ public static void assertFalse(boolean condition, 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}. */ - public static void assertFalse(boolean condition, Supplier messageSupplier) { + public static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { AssertFalse.assertFalse(condition, messageSupplier); } @@ -258,7 +264,7 @@ public static void assertFalse(BooleanSupplier booleanSupplier) { * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. *

Fails with the supplied failure {@code message}. */ - public static void assertFalse(BooleanSupplier booleanSupplier, String message) { + public static void assertFalse(BooleanSupplier booleanSupplier, @Nullable String message) { AssertFalse.assertFalse(booleanSupplier, message); } @@ -266,7 +272,7 @@ public static void assertFalse(BooleanSupplier booleanSupplier, String message) * Assert that the boolean condition supplied by {@code booleanSupplier} is {@code false}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertFalse(BooleanSupplier booleanSupplier, Supplier messageSupplier) { + public static void assertFalse(BooleanSupplier booleanSupplier, Supplier<@Nullable String> messageSupplier) { AssertFalse.assertFalse(booleanSupplier, messageSupplier); } @@ -275,7 +281,7 @@ public static void assertFalse(BooleanSupplier booleanSupplier, Supplier /** * Assert that {@code actual} is {@code null}. */ - public static void assertNull(Object actual) { + public static void assertNull(@Nullable Object actual) { AssertNull.assertNull(actual); } @@ -283,7 +289,7 @@ public static void assertNull(Object actual) { * Assert that {@code actual} is {@code null}. *

Fails with the supplied failure {@code message}. */ - public static void assertNull(Object actual, String message) { + public static void assertNull(@Nullable Object actual, @Nullable String message) { AssertNull.assertNull(actual, message); } @@ -291,7 +297,7 @@ public static void assertNull(Object actual, String message) { * Assert that {@code actual} is {@code null}. *

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

Fails with the supplied failure {@code message}. */ - public static void assertNotNull(Object actual, String message) { + public static void assertNotNull(@Nullable Object actual, @Nullable String message) { AssertNotNull.assertNotNull(actual, message); } @@ -316,7 +322,7 @@ public static void assertNotNull(Object actual, String message) { * Assert that {@code actual} is not {@code null}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertNotNull(Object actual, Supplier messageSupplier) { + public static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNotNull.assertNotNull(actual, messageSupplier); } @@ -332,14 +338,14 @@ public static void assertEquals(short expected, short actual) { /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(short expected, Short actual) { + public static void assertEquals(short expected, @Nullable Short actual) { AssertEquals.assertEquals((Short) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(Short expected, short actual) { + public static void assertEquals(@Nullable Short expected, short actual) { AssertEquals.assertEquals(expected, (Short) actual); } @@ -349,7 +355,7 @@ public static void assertEquals(Short expected, short actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Short expected, Short actual) { + public static void assertEquals(@Nullable Short expected, @Nullable Short actual) { AssertEquals.assertEquals(expected, actual); } @@ -357,7 +363,7 @@ public static void assertEquals(Short expected, Short actual) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(short expected, short actual, String message) { + public static void assertEquals(short expected, short actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -365,7 +371,7 @@ public static void assertEquals(short expected, short actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(short expected, Short actual, String message) { + public static void assertEquals(short expected, @Nullable Short actual, @Nullable String message) { AssertEquals.assertEquals((Short) expected, actual, message); } @@ -373,7 +379,7 @@ public static void assertEquals(short expected, Short actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Short expected, short actual, String message) { + public static void assertEquals(@Nullable Short expected, short actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Short) actual, message); } @@ -384,7 +390,7 @@ public static void assertEquals(Short expected, short actual, String message) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Short expected, Short actual, String message) { + public static void assertEquals(@Nullable Short expected, @Nullable Short actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -392,7 +398,7 @@ public static void assertEquals(Short expected, Short actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(short expected, short actual, Supplier messageSupplier) { + public static void assertEquals(short expected, short actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -400,7 +406,8 @@ public static void assertEquals(short expected, short actual, Supplier m * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(short expected, Short actual, Supplier messageSupplier) { + public static void assertEquals(short expected, @Nullable Short actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Short) expected, actual, messageSupplier); } @@ -408,7 +415,8 @@ public static void assertEquals(short expected, Short actual, Supplier m * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Short expected, short actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Short expected, short actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Short) actual, messageSupplier); } @@ -419,7 +427,8 @@ public static void assertEquals(Short expected, short actual, Supplier m * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Short expected, Short actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Short expected, @Nullable Short actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -433,14 +442,14 @@ public static void assertEquals(byte expected, byte actual) { /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(byte expected, Byte actual) { + public static void assertEquals(byte expected, @Nullable Byte actual) { AssertEquals.assertEquals((Byte) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(Byte expected, byte actual) { + public static void assertEquals(@Nullable Byte expected, byte actual) { AssertEquals.assertEquals(expected, (Byte) actual); } @@ -450,7 +459,7 @@ public static void assertEquals(Byte expected, byte actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Byte expected, Byte actual) { + public static void assertEquals(@Nullable Byte expected, @Nullable Byte actual) { AssertEquals.assertEquals(expected, actual); } @@ -458,7 +467,7 @@ public static void assertEquals(Byte expected, Byte actual) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(byte expected, byte actual, String message) { + public static void assertEquals(byte expected, byte actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -466,7 +475,7 @@ public static void assertEquals(byte expected, byte actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(byte expected, Byte actual, String message) { + public static void assertEquals(byte expected, @Nullable Byte actual, @Nullable String message) { AssertEquals.assertEquals((Byte) expected, actual, message); } @@ -474,7 +483,7 @@ public static void assertEquals(byte expected, Byte actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Byte expected, byte actual, String message) { + public static void assertEquals(@Nullable Byte expected, byte actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Byte) actual, message); } @@ -485,7 +494,7 @@ public static void assertEquals(Byte expected, byte actual, String message) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Byte expected, Byte actual, String message) { + public static void assertEquals(@Nullable Byte expected, @Nullable Byte actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -493,7 +502,7 @@ public static void assertEquals(Byte expected, Byte actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(byte expected, byte actual, Supplier messageSupplier) { + public static void assertEquals(byte expected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -501,7 +510,7 @@ public static void assertEquals(byte expected, byte actual, Supplier mes * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(byte expected, Byte actual, Supplier messageSupplier) { + public static void assertEquals(byte expected, @Nullable Byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Byte) expected, actual, messageSupplier); } @@ -509,7 +518,7 @@ public static void assertEquals(byte expected, Byte actual, Supplier mes * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Byte expected, byte actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Byte expected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Byte) actual, messageSupplier); } @@ -520,7 +529,8 @@ public static void assertEquals(Byte expected, byte actual, Supplier mes * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Byte expected, Byte actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Byte expected, @Nullable Byte actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -534,14 +544,14 @@ public static void assertEquals(int expected, int actual) { /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(int expected, Integer actual) { + public static void assertEquals(int expected, @Nullable Integer actual) { AssertEquals.assertEquals((Integer) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(Integer expected, int actual) { + public static void assertEquals(@Nullable Integer expected, int actual) { AssertEquals.assertEquals(expected, (Integer) actual); } @@ -551,7 +561,7 @@ public static void assertEquals(Integer expected, int actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Integer expected, Integer actual) { + public static void assertEquals(@Nullable Integer expected, @Nullable Integer actual) { AssertEquals.assertEquals(expected, actual); } @@ -559,7 +569,7 @@ public static void assertEquals(Integer expected, Integer actual) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(int expected, int actual, String message) { + public static void assertEquals(int expected, int actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -567,7 +577,7 @@ public static void assertEquals(int expected, int actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(int expected, Integer actual, String message) { + public static void assertEquals(int expected, @Nullable Integer actual, @Nullable String message) { AssertEquals.assertEquals((Integer) expected, actual, message); } @@ -575,7 +585,7 @@ public static void assertEquals(int expected, Integer actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Integer expected, int actual, String message) { + public static void assertEquals(@Nullable Integer expected, int actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Integer) actual, message); } @@ -586,7 +596,7 @@ public static void assertEquals(Integer expected, int actual, String message) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Integer expected, Integer actual, String message) { + public static void assertEquals(@Nullable Integer expected, @Nullable Integer actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -594,7 +604,7 @@ public static void assertEquals(Integer expected, Integer actual, String message * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(int expected, int actual, Supplier messageSupplier) { + public static void assertEquals(int expected, int actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -602,7 +612,8 @@ public static void assertEquals(int expected, int actual, Supplier messa * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(int expected, Integer actual, Supplier messageSupplier) { + public static void assertEquals(int expected, @Nullable Integer actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Integer) expected, actual, messageSupplier); } @@ -610,7 +621,8 @@ public static void assertEquals(int expected, Integer actual, Supplier m * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Integer expected, int actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Integer expected, int actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Integer) actual, messageSupplier); } @@ -621,7 +633,8 @@ public static void assertEquals(Integer expected, int actual, Supplier m * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Integer expected, Integer actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Integer expected, @Nullable Integer actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -635,14 +648,14 @@ public static void assertEquals(long expected, long actual) { /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(long expected, Long actual) { + public static void assertEquals(long expected, @Nullable Long actual) { AssertEquals.assertEquals((Long) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(Long expected, long actual) { + public static void assertEquals(@Nullable Long expected, long actual) { AssertEquals.assertEquals(expected, (Long) actual); } @@ -652,7 +665,7 @@ public static void assertEquals(Long expected, long actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Long expected, Long actual) { + public static void assertEquals(@Nullable Long expected, @Nullable Long actual) { AssertEquals.assertEquals(expected, actual); } @@ -660,7 +673,7 @@ public static void assertEquals(Long expected, Long actual) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(long expected, long actual, String message) { + public static void assertEquals(long expected, long actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -668,7 +681,7 @@ public static void assertEquals(long expected, long actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(long expected, Long actual, String message) { + public static void assertEquals(long expected, @Nullable Long actual, @Nullable String message) { AssertEquals.assertEquals((Long) expected, actual, message); } @@ -676,7 +689,7 @@ public static void assertEquals(long expected, Long actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Long expected, long actual, String message) { + public static void assertEquals(@Nullable Long expected, long actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Long) actual, message); } @@ -687,7 +700,7 @@ public static void assertEquals(Long expected, long actual, String message) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Long expected, Long actual, String message) { + public static void assertEquals(@Nullable Long expected, @Nullable Long actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -695,7 +708,7 @@ public static void assertEquals(Long expected, Long actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(long expected, long actual, Supplier messageSupplier) { + public static void assertEquals(long expected, long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -703,7 +716,7 @@ public static void assertEquals(long expected, long actual, Supplier mes * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(long expected, Long actual, Supplier messageSupplier) { + public static void assertEquals(long expected, @Nullable Long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Long) expected, actual, messageSupplier); } @@ -711,7 +724,7 @@ public static void assertEquals(long expected, Long actual, Supplier mes * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Long expected, long actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Long expected, long actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Long) actual, messageSupplier); } @@ -722,7 +735,8 @@ public static void assertEquals(Long expected, long actual, Supplier mes * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Long expected, Long actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Long expected, @Nullable Long actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -740,7 +754,7 @@ public static void assertEquals(float expected, float actual) { *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ - public static void assertEquals(float expected, Float actual) { + public static void assertEquals(float expected, @Nullable Float actual) { AssertEquals.assertEquals((Float) expected, actual); } @@ -749,7 +763,7 @@ public static void assertEquals(float expected, Float actual) { *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ - public static void assertEquals(Float expected, float actual) { + public static void assertEquals(@Nullable Float expected, float actual) { AssertEquals.assertEquals(expected, (Float) actual); } @@ -761,7 +775,7 @@ public static void assertEquals(Float expected, float actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Float expected, Float actual) { + public static void assertEquals(@Nullable Float expected, @Nullable Float actual) { AssertEquals.assertEquals(expected, actual); } @@ -771,7 +785,7 @@ public static void assertEquals(Float expected, Float actual) { * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(float expected, float actual, String message) { + public static void assertEquals(float expected, float actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -781,7 +795,7 @@ public static void assertEquals(float expected, float actual, String message) { * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(float expected, Float actual, String message) { + public static void assertEquals(float expected, @Nullable Float actual, @Nullable String message) { AssertEquals.assertEquals((Float) expected, actual, message); } @@ -791,7 +805,7 @@ public static void assertEquals(float expected, Float actual, String message) { * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Float expected, float actual, String message) { + public static void assertEquals(@Nullable Float expected, float actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Float) actual, message); } @@ -804,7 +818,7 @@ public static void assertEquals(Float expected, float actual, String message) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Float expected, Float actual, String message) { + public static void assertEquals(@Nullable Float expected, @Nullable Float actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -814,7 +828,7 @@ public static void assertEquals(Float expected, Float actual, String message) { * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(float expected, float actual, Supplier messageSupplier) { + public static void assertEquals(float expected, float actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -824,7 +838,8 @@ public static void assertEquals(float expected, float actual, Supplier m * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(float expected, Float actual, Supplier messageSupplier) { + public static void assertEquals(float expected, @Nullable Float actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Float) expected, actual, messageSupplier); } @@ -834,7 +849,8 @@ public static void assertEquals(float expected, Float actual, Supplier m * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Float expected, float actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Float expected, float actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Float) actual, messageSupplier); } @@ -847,7 +863,8 @@ public static void assertEquals(Float expected, float actual, Supplier m * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Float expected, Float actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Float expected, @Nullable Float actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -866,7 +883,7 @@ public static void assertEquals(float expected, float actual, float delta) { * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(float expected, float actual, float delta, String message) { + public static void assertEquals(float expected, float actual, float delta, @Nullable String message) { AssertEquals.assertEquals(expected, actual, delta, message); } @@ -876,7 +893,8 @@ public static void assertEquals(float expected, float actual, float delta, Strin * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(float expected, float actual, float delta, Supplier messageSupplier) { + public static void assertEquals(float expected, float actual, float delta, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, delta, messageSupplier); } @@ -894,7 +912,7 @@ public static void assertEquals(double expected, double actual) { *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ - public static void assertEquals(double expected, Double actual) { + public static void assertEquals(double expected, @Nullable Double actual) { AssertEquals.assertEquals((Double) expected, actual); } @@ -903,7 +921,7 @@ public static void assertEquals(double expected, Double actual) { *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ - public static void assertEquals(Double expected, double actual) { + public static void assertEquals(@Nullable Double expected, double actual) { AssertEquals.assertEquals(expected, (Double) actual); } @@ -915,7 +933,7 @@ public static void assertEquals(Double expected, double actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Double expected, Double actual) { + public static void assertEquals(@Nullable Double expected, @Nullable Double actual) { AssertEquals.assertEquals(expected, actual); } @@ -925,7 +943,7 @@ public static void assertEquals(Double expected, Double actual) { * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(double expected, double actual, String message) { + public static void assertEquals(double expected, double actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -935,7 +953,7 @@ public static void assertEquals(double expected, double actual, String message) * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(double expected, Double actual, String message) { + public static void assertEquals(double expected, @Nullable Double actual, @Nullable String message) { AssertEquals.assertEquals((Double) expected, actual, message); } @@ -945,7 +963,7 @@ public static void assertEquals(double expected, Double actual, String message) * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Double expected, double actual, String message) { + public static void assertEquals(@Nullable Double expected, double actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Double) actual, message); } @@ -958,7 +976,7 @@ public static void assertEquals(Double expected, double actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Double expected, Double actual, String message) { + public static void assertEquals(@Nullable Double expected, @Nullable Double actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -968,7 +986,7 @@ public static void assertEquals(Double expected, Double actual, String message) * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(double expected, double actual, Supplier messageSupplier) { + public static void assertEquals(double expected, double actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -978,7 +996,8 @@ public static void assertEquals(double expected, double actual, Supplier * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(double expected, Double actual, Supplier messageSupplier) { + public static void assertEquals(double expected, @Nullable Double actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Double) expected, actual, messageSupplier); } @@ -988,7 +1007,8 @@ public static void assertEquals(double expected, Double actual, Supplier * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Double expected, double actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Double expected, double actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Double) actual, messageSupplier); } @@ -1001,7 +1021,8 @@ public static void assertEquals(Double expected, double actual, Supplier * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Double expected, Double actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Double expected, @Nullable Double actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -1020,7 +1041,7 @@ public static void assertEquals(double expected, double actual, double delta) { * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(double expected, double actual, double delta, String message) { + public static void assertEquals(double expected, double actual, double delta, @Nullable String message) { AssertEquals.assertEquals(expected, actual, delta, message); } @@ -1030,7 +1051,8 @@ public static void assertEquals(double expected, double actual, double delta, St * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(double expected, double actual, double delta, Supplier messageSupplier) { + public static void assertEquals(double expected, double actual, double delta, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, delta, messageSupplier); } @@ -1044,14 +1066,14 @@ public static void assertEquals(char expected, char actual) { /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(char expected, Character actual) { + public static void assertEquals(char expected, @Nullable Character actual) { AssertEquals.assertEquals((Character) expected, actual); } /** * Assert that {@code expected} and {@code actual} are equal. */ - public static void assertEquals(Character expected, char actual) { + public static void assertEquals(@Nullable Character expected, char actual) { AssertEquals.assertEquals(expected, (Character) actual); } @@ -1061,7 +1083,7 @@ public static void assertEquals(Character expected, char actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Character expected, Character actual) { + public static void assertEquals(@Nullable Character expected, @Nullable Character actual) { AssertEquals.assertEquals(expected, actual); } @@ -1069,7 +1091,7 @@ public static void assertEquals(Character expected, Character actual) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(char expected, char actual, String message) { + public static void assertEquals(char expected, char actual, @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -1077,7 +1099,7 @@ public static void assertEquals(char expected, char actual, String message) { * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(char expected, Character actual, String message) { + public static void assertEquals(char expected, @Nullable Character actual, @Nullable String message) { AssertEquals.assertEquals((Character) expected, actual, message); } @@ -1085,7 +1107,7 @@ public static void assertEquals(char expected, Character actual, String message) * Assert that {@code expected} and {@code actual} are equal. *

Fails with the supplied failure {@code message}. */ - public static void assertEquals(Character expected, char actual, String message) { + public static void assertEquals(@Nullable Character expected, char actual, @Nullable String message) { AssertEquals.assertEquals(expected, (Character) actual, message); } @@ -1096,7 +1118,8 @@ public static void assertEquals(Character expected, char actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertEquals(Character expected, Character actual, String message) { + public static void assertEquals(@Nullable Character expected, @Nullable Character actual, + @Nullable String message) { AssertEquals.assertEquals(expected, actual, message); } @@ -1104,7 +1127,7 @@ public static void assertEquals(Character expected, Character actual, String mes * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(char expected, char actual, Supplier messageSupplier) { + public static void assertEquals(char expected, char actual, Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -1112,7 +1135,8 @@ public static void assertEquals(char expected, char actual, Supplier mes * Assert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(char expected, Character actual, Supplier messageSupplier) { + public static void assertEquals(char expected, @Nullable Character actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals((Character) expected, actual, messageSupplier); } @@ -1120,7 +1144,8 @@ public static void assertEquals(char expected, Character actual, SupplierAssert that {@code expected} and {@code actual} are equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertEquals(Character expected, char actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Character expected, char actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, (Character) actual, messageSupplier); } @@ -1131,7 +1156,8 @@ public static void assertEquals(Character expected, char actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Character expected, @Nullable Character actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -1141,7 +1167,7 @@ public static void assertEquals(Character expected, Character actual, Supplier messageSupplier) { + public static void assertEquals(@Nullable Object expected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { AssertEquals.assertEquals(expected, actual, messageSupplier); } @@ -1173,7 +1200,7 @@ public static void assertEquals(Object expected, Object actual, Supplier * Assert that {@code expected} and {@code actual} boolean arrays are equal. *

If both are {@code null}, they are considered equal. */ - public static void assertArrayEquals(boolean[] expected, boolean[] actual) { + public static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1182,7 +1209,8 @@ public static void assertArrayEquals(boolean[] expected, boolean[] actual) { *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(boolean[] expected, boolean[] actual, String message) { + public static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1191,7 +1219,8 @@ public static void assertArrayEquals(boolean[] expected, boolean[] actual, Strin *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(boolean[] expected, boolean[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(boolean @Nullable [] expected, boolean @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1199,7 +1228,7 @@ public static void assertArrayEquals(boolean[] expected, boolean[] actual, Suppl * Assert that {@code expected} and {@code actual} char arrays are equal. *

If both are {@code null}, they are considered equal. */ - public static void assertArrayEquals(char[] expected, char[] actual) { + public static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1208,7 +1237,8 @@ public static void assertArrayEquals(char[] expected, char[] actual) { *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(char[] expected, char[] actual, String message) { + public static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1217,7 +1247,8 @@ public static void assertArrayEquals(char[] expected, char[] actual, String mess *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(char[] expected, char[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(char @Nullable [] expected, char @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1225,7 +1256,7 @@ public static void assertArrayEquals(char[] expected, char[] actual, SupplierAssert that {@code expected} and {@code actual} byte arrays are equal. *

If both are {@code null}, they are considered equal. */ - public static void assertArrayEquals(byte[] expected, byte[] actual) { + public static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1234,7 +1265,8 @@ public static void assertArrayEquals(byte[] expected, byte[] actual) { *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(byte[] expected, byte[] actual, String message) { + public static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1243,7 +1275,8 @@ public static void assertArrayEquals(byte[] expected, byte[] actual, String mess *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(byte[] expected, byte[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(byte @Nullable [] expected, byte @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1251,7 +1284,7 @@ public static void assertArrayEquals(byte[] expected, byte[] actual, SupplierAssert that {@code expected} and {@code actual} short arrays are equal. *

If both are {@code null}, they are considered equal. */ - public static void assertArrayEquals(short[] expected, short[] actual) { + public static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1260,7 +1293,8 @@ public static void assertArrayEquals(short[] expected, short[] actual) { *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(short[] expected, short[] actual, String message) { + public static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1269,7 +1303,8 @@ public static void assertArrayEquals(short[] expected, short[] actual, String me *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(short[] expected, short[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(short @Nullable [] expected, short @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1277,7 +1312,7 @@ public static void assertArrayEquals(short[] expected, short[] actual, Supplier< * Assert that {@code expected} and {@code actual} int arrays are equal. *

If both are {@code null}, they are considered equal. */ - public static void assertArrayEquals(int[] expected, int[] actual) { + public static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1286,7 +1321,7 @@ public static void assertArrayEquals(int[] expected, int[] actual) { *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(int[] expected, int[] actual, String message) { + public static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1295,7 +1330,8 @@ public static void assertArrayEquals(int[] expected, int[] actual, String messag *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(int[] expected, int[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(int @Nullable [] expected, int @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1303,7 +1339,7 @@ public static void assertArrayEquals(int[] expected, int[] actual, SupplierAssert that {@code expected} and {@code actual} long arrays are equal. *

If both are {@code null}, they are considered equal. */ - public static void assertArrayEquals(long[] expected, long[] actual) { + public static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1312,7 +1348,8 @@ public static void assertArrayEquals(long[] expected, long[] actual) { *

If both are {@code null}, they are considered equal. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(long[] expected, long[] actual, String message) { + public static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1321,7 +1358,8 @@ public static void assertArrayEquals(long[] expected, long[] actual, String mess *

If both are {@code null}, they are considered equal. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(long[] expected, long[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(long @Nullable [] expected, long @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1330,7 +1368,7 @@ public static void assertArrayEquals(long[] expected, long[] actual, SupplierEquality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ - public static void assertArrayEquals(float[] expected, float[] actual) { + public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1340,7 +1378,8 @@ public static void assertArrayEquals(float[] expected, float[] actual) { * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(float[] expected, float[] actual, String message) { + public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1350,7 +1389,8 @@ public static void assertArrayEquals(float[] expected, float[] actual, String me * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(float[] expected, float[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1359,7 +1399,7 @@ public static void assertArrayEquals(float[] expected, float[] actual, Supplier< *

Equality imposed by this method is consistent with {@link Float#equals(Object)} and * {@link Float#compare(float, float)}. */ - public static void assertArrayEquals(float[] expected, float[] actual, float delta) { + public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta) { AssertArrayEquals.assertArrayEquals(expected, actual, delta); } @@ -1369,7 +1409,8 @@ public static void assertArrayEquals(float[] expected, float[] actual, float del * {@link Float#compare(float, float)}. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(float[] expected, float[] actual, float delta, String message) { + public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); } @@ -1379,8 +1420,8 @@ public static void assertArrayEquals(float[] expected, float[] actual, float del * {@link Float#compare(float, float)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(float[] expected, float[] actual, float delta, - Supplier messageSupplier) { + public static void assertArrayEquals(float @Nullable [] expected, float @Nullable [] actual, float delta, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); } @@ -1389,7 +1430,7 @@ public static void assertArrayEquals(float[] expected, float[] actual, float del *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ - public static void assertArrayEquals(double[] expected, double[] actual) { + public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1399,7 +1440,8 @@ public static void assertArrayEquals(double[] expected, double[] actual) { * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(double[] expected, double[] actual, String message) { + public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1409,7 +1451,8 @@ public static void assertArrayEquals(double[] expected, double[] actual, String * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(double[] expected, double[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1418,7 +1461,7 @@ public static void assertArrayEquals(double[] expected, double[] actual, Supplie *

Equality imposed by this method is consistent with {@link Double#equals(Object)} and * {@link Double#compare(double, double)}. */ - public static void assertArrayEquals(double[] expected, double[] actual, double delta) { + public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta) { AssertArrayEquals.assertArrayEquals(expected, actual, delta); } @@ -1428,7 +1471,8 @@ public static void assertArrayEquals(double[] expected, double[] actual, double * {@link Double#compare(double, double)}. *

Fails with the supplied failure {@code message}. */ - public static void assertArrayEquals(double[] expected, double[] actual, double delta, String message) { + public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, message); } @@ -1438,8 +1482,8 @@ public static void assertArrayEquals(double[] expected, double[] actual, double * {@link Double#compare(double, double)}. *

If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ - public static void assertArrayEquals(double[] expected, double[] actual, double delta, - Supplier messageSupplier) { + public static void assertArrayEquals(double @Nullable [] expected, double @Nullable [] actual, double delta, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, delta, messageSupplier); } @@ -1452,7 +1496,7 @@ public static void assertArrayEquals(double[] expected, double[] actual, double * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) */ - public static void assertArrayEquals(Object[] expected, Object[] actual) { + public static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual) { AssertArrayEquals.assertArrayEquals(expected, actual); } @@ -1466,7 +1510,8 @@ public static void assertArrayEquals(Object[] expected, Object[] actual) { * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) */ - public static void assertArrayEquals(Object[] expected, Object[] actual, String message) { + public static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, + @Nullable String message) { AssertArrayEquals.assertArrayEquals(expected, actual, message); } @@ -1480,7 +1525,8 @@ public static void assertArrayEquals(Object[] expected, Object[] actual, String * @see Objects#equals(Object, Object) * @see Arrays#deepEquals(Object[], Object[]) */ - public static void assertArrayEquals(Object[] expected, Object[] actual, Supplier messageSupplier) { + public static void assertArrayEquals(@Nullable Object @Nullable [] expected, @Nullable Object @Nullable [] actual, + Supplier<@Nullable String> messageSupplier) { AssertArrayEquals.assertArrayEquals(expected, actual, messageSupplier); } @@ -1504,7 +1550,7 @@ public static void assertArrayEquals(Object[] expected, Object[] actual, Supplie * @see Arrays#deepEquals(Object[], Object[]) * @see #assertArrayEquals(Object[], Object[]) */ - public static void assertIterableEquals(Iterable expected, Iterable actual) { + public static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual) { AssertIterableEquals.assertIterableEquals(expected, actual); } @@ -1528,7 +1574,8 @@ public static void assertIterableEquals(Iterable expected, Iterable actual * @see Arrays#deepEquals(Object[], Object[]) * @see #assertArrayEquals(Object[], Object[], String) */ - public static void assertIterableEquals(Iterable expected, Iterable actual, String message) { + public static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, + @Nullable String message) { AssertIterableEquals.assertIterableEquals(expected, actual, message); } @@ -1552,8 +1599,8 @@ public static void assertIterableEquals(Iterable expected, Iterable actual * @see Arrays#deepEquals(Object[], Object[]) * @see #assertArrayEquals(Object[], Object[], Supplier) */ - public static void assertIterableEquals(Iterable expected, Iterable actual, - Supplier messageSupplier) { + public static void assertIterableEquals(@Nullable Iterable expected, @Nullable Iterable actual, + Supplier<@Nullable String> messageSupplier) { AssertIterableEquals.assertIterableEquals(expected, actual, messageSupplier); } @@ -1613,7 +1660,8 @@ public static void assertLinesMatch(List expectedLines, List act * * @see #assertLinesMatch(List, List) */ - public static void assertLinesMatch(List expectedLines, List actualLines, String message) { + public static void assertLinesMatch(List expectedLines, List actualLines, + @Nullable String message) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); } @@ -1630,7 +1678,7 @@ public static void assertLinesMatch(List expectedLines, List act * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(List expectedLines, List actualLines, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); } @@ -1664,7 +1712,8 @@ public static void assertLinesMatch(Stream expectedLines, Stream * @since 5.7 * @see #assertLinesMatch(List, List) */ - public static void assertLinesMatch(Stream expectedLines, Stream actualLines, String message) { + public static void assertLinesMatch(Stream expectedLines, Stream actualLines, + @Nullable String message) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, message); } @@ -1685,7 +1734,7 @@ public static void assertLinesMatch(Stream expectedLines, Stream * @see #assertLinesMatch(List, List) */ public static void assertLinesMatch(Stream expectedLines, Stream actualLines, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { AssertLinesMatch.assertLinesMatch(expectedLines, actualLines, messageSupplier); } @@ -1707,7 +1756,7 @@ public static void assertNotEquals(byte unexpected, byte actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, Byte actual) { + public static void assertNotEquals(byte unexpected, @Nullable Byte actual) { AssertNotEquals.assertNotEquals((Byte) unexpected, actual); } @@ -1717,7 +1766,7 @@ public static void assertNotEquals(byte unexpected, Byte actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, byte actual) { + public static void assertNotEquals(@Nullable Byte unexpected, byte actual) { AssertNotEquals.assertNotEquals(unexpected, (Byte) actual); } @@ -1727,7 +1776,7 @@ public static void assertNotEquals(Byte unexpected, byte actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, Byte actual) { + public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -1739,7 +1788,7 @@ public static void assertNotEquals(Byte unexpected, Byte actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, byte actual, String message) { + public static void assertNotEquals(byte unexpected, byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -1751,7 +1800,7 @@ public static void assertNotEquals(byte unexpected, byte actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, Byte actual, String message) { + public static void assertNotEquals(byte unexpected, @Nullable Byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Byte) unexpected, actual, message); } @@ -1763,7 +1812,7 @@ public static void assertNotEquals(byte unexpected, Byte actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, byte actual, String message) { + public static void assertNotEquals(@Nullable Byte unexpected, byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, message); } @@ -1775,7 +1824,7 @@ public static void assertNotEquals(Byte unexpected, byte actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Byte unexpected, Byte actual, String message) { + public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -1788,7 +1837,7 @@ public static void assertNotEquals(Byte unexpected, Byte actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { + public static void assertNotEquals(byte unexpected, byte actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -1801,7 +1850,8 @@ public static void assertNotEquals(byte unexpected, byte actual, Supplier messageSupplier) { + public static void assertNotEquals(byte unexpected, @Nullable Byte actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Byte) unexpected, actual, messageSupplier); } @@ -1814,7 +1864,8 @@ public static void assertNotEquals(byte unexpected, Byte actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Byte unexpected, byte actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Byte) actual, messageSupplier); } @@ -1827,7 +1878,8 @@ public static void assertNotEquals(Byte unexpected, byte actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -1847,7 +1899,7 @@ public static void assertNotEquals(short unexpected, short actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, Short actual) { + public static void assertNotEquals(short unexpected, @Nullable Short actual) { AssertNotEquals.assertNotEquals((Short) unexpected, actual); } @@ -1857,7 +1909,7 @@ public static void assertNotEquals(short unexpected, Short actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, short actual) { + public static void assertNotEquals(@Nullable Short unexpected, short actual) { AssertNotEquals.assertNotEquals(unexpected, (Short) actual); } @@ -1867,7 +1919,7 @@ public static void assertNotEquals(Short unexpected, short actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, Short actual) { + public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -1879,7 +1931,7 @@ public static void assertNotEquals(Short unexpected, Short actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, short actual, String message) { + public static void assertNotEquals(short unexpected, short actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -1891,7 +1943,7 @@ public static void assertNotEquals(short unexpected, short actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, Short actual, String message) { + public static void assertNotEquals(short unexpected, @Nullable Short actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Short) unexpected, actual, message); } @@ -1903,7 +1955,7 @@ public static void assertNotEquals(short unexpected, Short actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, short actual, String message) { + public static void assertNotEquals(@Nullable Short unexpected, short actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Short) actual, message); } @@ -1915,7 +1967,7 @@ public static void assertNotEquals(Short unexpected, short actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Short unexpected, Short actual, String message) { + public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -1928,7 +1980,7 @@ public static void assertNotEquals(Short unexpected, Short actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { + public static void assertNotEquals(short unexpected, short actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -1941,7 +1993,8 @@ public static void assertNotEquals(short unexpected, short actual, Supplier messageSupplier) { + public static void assertNotEquals(short unexpected, @Nullable Short actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Short) unexpected, actual, messageSupplier); } @@ -1954,7 +2007,8 @@ public static void assertNotEquals(short unexpected, Short actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Short unexpected, short actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Short) actual, messageSupplier); } @@ -1967,7 +2021,8 @@ public static void assertNotEquals(Short unexpected, short actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -1987,7 +2042,7 @@ public static void assertNotEquals(int unexpected, int actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, Integer actual) { + public static void assertNotEquals(int unexpected, @Nullable Integer actual) { AssertNotEquals.assertNotEquals((Integer) unexpected, actual); } @@ -1997,7 +2052,7 @@ public static void assertNotEquals(int unexpected, Integer actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, int actual) { + public static void assertNotEquals(@Nullable Integer unexpected, int actual) { AssertNotEquals.assertNotEquals(unexpected, (Integer) actual); } @@ -2007,7 +2062,7 @@ public static void assertNotEquals(Integer unexpected, int actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, Integer actual) { + public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integer actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -2019,7 +2074,7 @@ public static void assertNotEquals(Integer unexpected, Integer actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, int actual, String message) { + public static void assertNotEquals(int unexpected, int actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2031,7 +2086,7 @@ public static void assertNotEquals(int unexpected, int actual, String message) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, Integer actual, String message) { + public static void assertNotEquals(int unexpected, @Nullable Integer actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Integer) unexpected, actual, message); } @@ -2043,7 +2098,7 @@ public static void assertNotEquals(int unexpected, Integer actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, int actual, String message) { + public static void assertNotEquals(@Nullable Integer unexpected, int actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, message); } @@ -2055,7 +2110,8 @@ public static void assertNotEquals(Integer unexpected, int actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Integer unexpected, Integer actual, String message) { + public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integer actual, + @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2068,7 +2124,7 @@ public static void assertNotEquals(Integer unexpected, Integer actual, String me * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, int actual, Supplier messageSupplier) { + public static void assertNotEquals(int unexpected, int actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2081,7 +2137,8 @@ public static void assertNotEquals(int unexpected, int actual, Supplier * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(int unexpected, Integer actual, Supplier messageSupplier) { + public static void assertNotEquals(int unexpected, @Nullable Integer actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Integer) unexpected, actual, messageSupplier); } @@ -2094,7 +2151,8 @@ public static void assertNotEquals(int unexpected, Integer actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Integer unexpected, int actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Integer) actual, messageSupplier); } @@ -2107,7 +2165,8 @@ public static void assertNotEquals(Integer unexpected, int actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integer actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2127,7 +2186,7 @@ public static void assertNotEquals(long unexpected, long actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, Long actual) { + public static void assertNotEquals(long unexpected, @Nullable Long actual) { AssertNotEquals.assertNotEquals((Long) unexpected, actual); } @@ -2137,7 +2196,7 @@ public static void assertNotEquals(long unexpected, Long actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, long actual) { + public static void assertNotEquals(@Nullable Long unexpected, long actual) { AssertNotEquals.assertNotEquals(unexpected, (Long) actual); } @@ -2147,7 +2206,7 @@ public static void assertNotEquals(Long unexpected, long actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, Long actual) { + public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -2159,7 +2218,7 @@ public static void assertNotEquals(Long unexpected, Long actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, long actual, String message) { + public static void assertNotEquals(long unexpected, long actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2171,7 +2230,7 @@ public static void assertNotEquals(long unexpected, long actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, Long actual, String message) { + public static void assertNotEquals(long unexpected, @Nullable Long actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Long) unexpected, actual, message); } @@ -2183,7 +2242,7 @@ public static void assertNotEquals(long unexpected, Long actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, long actual, String message) { + public static void assertNotEquals(@Nullable Long unexpected, long actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Long) actual, message); } @@ -2195,7 +2254,7 @@ public static void assertNotEquals(Long unexpected, long actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Long unexpected, Long actual, String message) { + public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2208,7 +2267,7 @@ public static void assertNotEquals(Long unexpected, Long actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { + public static void assertNotEquals(long unexpected, long actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2221,7 +2280,8 @@ public static void assertNotEquals(long unexpected, long actual, Supplier messageSupplier) { + public static void assertNotEquals(long unexpected, @Nullable Long actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Long) unexpected, actual, messageSupplier); } @@ -2234,7 +2294,8 @@ public static void assertNotEquals(long unexpected, Long actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Long unexpected, long actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Long) actual, messageSupplier); } @@ -2247,7 +2308,8 @@ public static void assertNotEquals(Long unexpected, long actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2273,7 +2335,7 @@ public static void assertNotEquals(float unexpected, float actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, Float actual) { + public static void assertNotEquals(float unexpected, @Nullable Float actual) { AssertNotEquals.assertNotEquals((Float) unexpected, actual); } @@ -2286,7 +2348,7 @@ public static void assertNotEquals(float unexpected, Float actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, float actual) { + public static void assertNotEquals(@Nullable Float unexpected, float actual) { AssertNotEquals.assertNotEquals(unexpected, (Float) actual); } @@ -2299,7 +2361,7 @@ public static void assertNotEquals(Float unexpected, float actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, Float actual) { + public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -2314,7 +2376,7 @@ public static void assertNotEquals(Float unexpected, Float actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, String message) { + public static void assertNotEquals(float unexpected, float actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2329,7 +2391,7 @@ public static void assertNotEquals(float unexpected, float actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, Float actual, String message) { + public static void assertNotEquals(float unexpected, @Nullable Float actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Float) unexpected, actual, message); } @@ -2344,7 +2406,7 @@ public static void assertNotEquals(float unexpected, Float actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, float actual, String message) { + public static void assertNotEquals(@Nullable Float unexpected, float actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Float) actual, message); } @@ -2359,7 +2421,7 @@ public static void assertNotEquals(Float unexpected, float actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Float unexpected, Float actual, String message) { + public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2372,7 +2434,7 @@ public static void assertNotEquals(Float unexpected, Float actual, String messag * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { + public static void assertNotEquals(float unexpected, float actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2385,7 +2447,8 @@ public static void assertNotEquals(float unexpected, float actual, Supplier messageSupplier) { + public static void assertNotEquals(float unexpected, @Nullable Float actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Float) unexpected, actual, messageSupplier); } @@ -2398,7 +2461,8 @@ public static void assertNotEquals(float unexpected, Float actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Float unexpected, float actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Float) actual, messageSupplier); } @@ -2411,7 +2475,8 @@ public static void assertNotEquals(Float unexpected, float actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2441,7 +2506,7 @@ public static void assertNotEquals(float unexpected, float actual, float delta) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, float delta, String message) { + public static void assertNotEquals(float unexpected, float actual, float delta, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); } @@ -2455,7 +2520,8 @@ public static void assertNotEquals(float unexpected, float actual, float delta, * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(float unexpected, float actual, float delta, Supplier messageSupplier) { + public static void assertNotEquals(float unexpected, float actual, float delta, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); } @@ -2481,7 +2547,7 @@ public static void assertNotEquals(double unexpected, double actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, Double actual) { + public static void assertNotEquals(double unexpected, @Nullable Double actual) { AssertNotEquals.assertNotEquals((Double) unexpected, actual); } @@ -2494,7 +2560,7 @@ public static void assertNotEquals(double unexpected, Double actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, double actual) { + public static void assertNotEquals(@Nullable Double unexpected, double actual) { AssertNotEquals.assertNotEquals(unexpected, (Double) actual); } @@ -2507,7 +2573,7 @@ public static void assertNotEquals(Double unexpected, double actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, Double actual) { + public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -2522,7 +2588,7 @@ public static void assertNotEquals(Double unexpected, Double actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, String message) { + public static void assertNotEquals(double unexpected, double actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2537,7 +2603,7 @@ public static void assertNotEquals(double unexpected, double actual, String mess * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, Double actual, String message) { + public static void assertNotEquals(double unexpected, @Nullable Double actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Double) unexpected, actual, message); } @@ -2552,7 +2618,7 @@ public static void assertNotEquals(double unexpected, Double actual, String mess * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, double actual, String message) { + public static void assertNotEquals(@Nullable Double unexpected, double actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Double) actual, message); } @@ -2567,7 +2633,7 @@ public static void assertNotEquals(Double unexpected, double actual, String mess * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Double unexpected, Double actual, String message) { + public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2580,7 +2646,7 @@ public static void assertNotEquals(Double unexpected, Double actual, String mess * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { + public static void assertNotEquals(double unexpected, double actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2593,7 +2659,8 @@ public static void assertNotEquals(double unexpected, double actual, Supplier messageSupplier) { + public static void assertNotEquals(double unexpected, @Nullable Double actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Double) unexpected, actual, messageSupplier); } @@ -2606,7 +2673,8 @@ public static void assertNotEquals(double unexpected, Double actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Double unexpected, double actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Double) actual, messageSupplier); } @@ -2619,7 +2687,8 @@ public static void assertNotEquals(Double unexpected, double actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2649,7 +2718,7 @@ public static void assertNotEquals(double unexpected, double actual, double delt * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(double unexpected, double actual, double delta, String message) { + public static void assertNotEquals(double unexpected, double actual, double delta, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, message); } @@ -2664,7 +2733,7 @@ public static void assertNotEquals(double unexpected, double actual, double delt */ @API(status = STABLE, since = "5.4") public static void assertNotEquals(double unexpected, double actual, double delta, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, delta, messageSupplier); } @@ -2684,7 +2753,7 @@ public static void assertNotEquals(char unexpected, char actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, Character actual) { + public static void assertNotEquals(char unexpected, @Nullable Character actual) { AssertNotEquals.assertNotEquals((Character) unexpected, actual); } @@ -2694,7 +2763,7 @@ public static void assertNotEquals(char unexpected, Character actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, char actual) { + public static void assertNotEquals(@Nullable Character unexpected, char actual) { AssertNotEquals.assertNotEquals(unexpected, (Character) actual); } @@ -2704,7 +2773,7 @@ public static void assertNotEquals(Character unexpected, char actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, Character actual) { + public static void assertNotEquals(@Nullable Character unexpected, @Nullable Character actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -2716,7 +2785,7 @@ public static void assertNotEquals(Character unexpected, Character actual) { * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, char actual, String message) { + public static void assertNotEquals(char unexpected, char actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2728,7 +2797,7 @@ public static void assertNotEquals(char unexpected, char actual, String message) * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, Character actual, String message) { + public static void assertNotEquals(char unexpected, @Nullable Character actual, @Nullable String message) { AssertNotEquals.assertNotEquals((Character) unexpected, actual, message); } @@ -2740,7 +2809,7 @@ public static void assertNotEquals(char unexpected, Character actual, String mes * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, char actual, String message) { + public static void assertNotEquals(@Nullable Character unexpected, char actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, (Character) actual, message); } @@ -2752,7 +2821,8 @@ public static void assertNotEquals(Character unexpected, char actual, String mes * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(Character unexpected, Character actual, String message) { + public static void assertNotEquals(@Nullable Character unexpected, @Nullable Character actual, + @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2765,7 +2835,7 @@ public static void assertNotEquals(Character unexpected, Character actual, Strin * @since 5.4 */ @API(status = STABLE, since = "5.4") - public static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { + public static void assertNotEquals(char unexpected, char actual, Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2778,7 +2848,8 @@ public static void assertNotEquals(char unexpected, char actual, Supplier messageSupplier) { + public static void assertNotEquals(char unexpected, @Nullable Character actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals((Character) unexpected, actual, messageSupplier); } @@ -2791,7 +2862,8 @@ public static void assertNotEquals(char unexpected, Character actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Character unexpected, char actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, (Character) actual, messageSupplier); } @@ -2804,7 +2876,8 @@ public static void assertNotEquals(Character unexpected, char actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Character unexpected, @Nullable Character actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2815,7 +2888,7 @@ public static void assertNotEquals(Character unexpected, Character actual, Suppl * * @see Object#equals(Object) */ - public static void assertNotEquals(Object unexpected, Object actual) { + public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual) { AssertNotEquals.assertNotEquals(unexpected, actual); } @@ -2828,7 +2901,7 @@ public static void assertNotEquals(Object unexpected, Object actual) { * * @see Object#equals(Object) */ - public static void assertNotEquals(Object unexpected, Object actual, String message) { + public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { AssertNotEquals.assertNotEquals(unexpected, actual, message); } @@ -2842,7 +2915,8 @@ public static void assertNotEquals(Object unexpected, Object actual, String mess * * @see Object#equals(Object) */ - public static void assertNotEquals(Object unexpected, Object actual, Supplier messageSupplier) { + public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { AssertNotEquals.assertNotEquals(unexpected, actual, messageSupplier); } @@ -2856,7 +2930,7 @@ public static void assertNotEquals(Object unexpected, Object actual, SupplierFails with the supplied failure {@code message}. */ - public static void assertSame(Object expected, Object actual, String message) { + public static void assertSame(@Nullable Object expected, @Nullable Object actual, @Nullable String message) { AssertSame.assertSame(expected, actual, message); } @@ -2883,7 +2957,8 @@ public static void assertSame(Object expected, Object actual, String message) { *

If necessary, the failure message will be retrieved lazily from the supplied * {@code messageSupplier}. */ - public static void assertSame(Object expected, Object actual, Supplier messageSupplier) { + public static void assertSame(@Nullable Object expected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { AssertSame.assertSame(expected, actual, messageSupplier); } @@ -2896,7 +2971,7 @@ public static void assertSame(Object expected, Object actual, Supplier m * objects. To assert that two objects or two primitive values are not * equal, use one of the {@code assertNotEquals(...)} methods instead. */ - public static void assertNotSame(Object unexpected, Object actual) { + public static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual) { AssertNotSame.assertNotSame(unexpected, actual); } @@ -2908,7 +2983,7 @@ public static void assertNotSame(Object unexpected, Object actual) { * equal, use one of the {@code assertNotEquals(...)} methods instead. *

Fails with the supplied failure {@code message}. */ - public static void assertNotSame(Object unexpected, Object actual, String message) { + public static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, @Nullable String message) { AssertNotSame.assertNotSame(unexpected, actual, message); } @@ -2921,7 +2996,8 @@ public static void assertNotSame(Object unexpected, Object actual, String messag *

If necessary, the failure message will be retrieved lazily from the supplied * {@code messageSupplier}. */ - public static void assertNotSame(Object unexpected, Object actual, Supplier messageSupplier) { + public static void assertNotSame(@Nullable Object unexpected, @Nullable Object actual, + Supplier<@Nullable String> messageSupplier) { AssertNotSame.assertNotSame(unexpected, actual, messageSupplier); } @@ -2957,7 +3033,7 @@ public static void assertAll(Executable... executables) throws MultipleFailuresE * @see #assertAll(String, Collection) * @see #assertAll(String, Stream) */ - public static void assertAll(String heading, Executable... executables) throws MultipleFailuresError { + public static void assertAll(@Nullable String heading, Executable... executables) throws MultipleFailuresError { AssertAll.assertAll(heading, executables); } @@ -2991,7 +3067,8 @@ public static void assertAll(Collection executables) throws Multiple * @see #assertAll(Stream) * @see #assertAll(String, Stream) */ - public static void assertAll(String heading, Collection executables) throws MultipleFailuresError { + public static void assertAll(@Nullable String heading, Collection executables) + throws MultipleFailuresError { AssertAll.assertAll(heading, executables); } @@ -3035,7 +3112,8 @@ public static void assertAll(Stream executables) throws MultipleFail * @see #assertAll(String, Collection) * @see #assertAll(Stream) */ - public static void assertAll(String heading, Stream executables) throws MultipleFailuresError { + public static void assertAll(@Nullable String heading, Stream executables) + throws MultipleFailuresError { AssertAll.assertAll(heading, executables); } @@ -3080,7 +3158,7 @@ public static T assertThrowsExactly(Class expectedType, */ @API(status = STABLE, since = "5.10") public static T assertThrowsExactly(Class expectedType, Executable executable, - String message) { + @Nullable String message) { return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, message); } @@ -3105,7 +3183,7 @@ public static T assertThrowsExactly(Class expectedType, */ @API(status = STABLE, since = "5.10") public static T assertThrowsExactly(Class expectedType, Executable executable, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { return AssertThrowsExactly.assertThrowsExactly(expectedType, executable, messageSupplier); } @@ -3149,7 +3227,8 @@ public static T assertThrows(Class expectedType, Execut * * @see #assertThrowsExactly(Class, Executable, String) */ - public static T assertThrows(Class expectedType, Executable executable, String message) { + public static T assertThrows(Class expectedType, Executable executable, + @Nullable String message) { return AssertThrows.assertThrows(expectedType, executable, message); } @@ -3176,7 +3255,7 @@ public static T assertThrows(Class expectedType, Execut * @see #assertThrowsExactly(Class, Executable, Supplier) */ public static T assertThrows(Class expectedType, Executable executable, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { return AssertThrows.assertThrows(expectedType, executable, messageSupplier); } @@ -3214,7 +3293,7 @@ public static void assertDoesNotThrow(Executable executable) { * @since 5.2 */ @API(status = STABLE, since = "5.2") - public static void assertDoesNotThrow(Executable executable, String message) { + public static void assertDoesNotThrow(Executable executable, @Nullable String message) { AssertDoesNotThrow.assertDoesNotThrow(executable, message); } @@ -3234,7 +3313,7 @@ public static void assertDoesNotThrow(Executable executable, String message) { * @since 5.2 */ @API(status = STABLE, since = "5.2") - public static void assertDoesNotThrow(Executable executable, Supplier messageSupplier) { + public static void assertDoesNotThrow(Executable executable, Supplier<@Nullable String> messageSupplier) { AssertDoesNotThrow.assertDoesNotThrow(executable, messageSupplier); } @@ -3255,7 +3334,7 @@ public static void assertDoesNotThrow(Executable executable, Supplier me * @since 5.2 */ @API(status = STABLE, since = "5.2") - public static T assertDoesNotThrow(ThrowingSupplier supplier) { + public static T assertDoesNotThrow(ThrowingSupplier supplier) { return AssertDoesNotThrow.assertDoesNotThrow(supplier); } @@ -3276,7 +3355,8 @@ public static T assertDoesNotThrow(ThrowingSupplier supplier) { * @since 5.2 */ @API(status = STABLE, since = "5.2") - public static T assertDoesNotThrow(ThrowingSupplier supplier, String message) { + public static T assertDoesNotThrow(ThrowingSupplier supplier, + @Nullable String message) { return AssertDoesNotThrow.assertDoesNotThrow(supplier, message); } @@ -3298,7 +3378,8 @@ public static T assertDoesNotThrow(ThrowingSupplier supplier, String mess * @since 5.2 */ @API(status = STABLE, since = "5.2") - public static T assertDoesNotThrow(ThrowingSupplier supplier, Supplier messageSupplier) { + public static T assertDoesNotThrow(ThrowingSupplier supplier, + Supplier<@Nullable String> messageSupplier) { return AssertDoesNotThrow.assertDoesNotThrow(supplier, messageSupplier); } @@ -3342,7 +3423,7 @@ public static void assertTimeout(Duration timeout, Executable executable) { * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable, String) */ - public static void assertTimeout(Duration timeout, Executable executable, String message) { + public static void assertTimeout(Duration timeout, Executable executable, @Nullable String message) { AssertTimeout.assertTimeout(timeout, executable, message); } @@ -3364,7 +3445,8 @@ public static void assertTimeout(Duration timeout, Executable executable, String * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) */ - public static void assertTimeout(Duration timeout, Executable executable, Supplier messageSupplier) { + public static void assertTimeout(Duration timeout, Executable executable, + Supplier<@Nullable String> messageSupplier) { AssertTimeout.assertTimeout(timeout, executable, messageSupplier); } @@ -3387,7 +3469,7 @@ public static void assertTimeout(Duration timeout, Executable executable, Suppli * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable) */ - public static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { + public static T assertTimeout(Duration timeout, ThrowingSupplier supplier) { return AssertTimeout.assertTimeout(timeout, supplier); } @@ -3410,7 +3492,8 @@ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier * @see #assertTimeout(Duration, ThrowingSupplier, Supplier) * @see #assertTimeoutPreemptively(Duration, Executable, String) */ - public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, String message) { + public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, + @Nullable String message) { return AssertTimeout.assertTimeout(timeout, supplier, message); } @@ -3434,8 +3517,8 @@ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier * @see #assertTimeout(Duration, ThrowingSupplier, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) */ - public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { + public static T assertTimeout(Duration timeout, ThrowingSupplier supplier, + Supplier<@Nullable String> messageSupplier) { return AssertTimeout.assertTimeout(timeout, supplier, messageSupplier); } @@ -3475,7 +3558,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable, String) */ - public static void assertTimeoutPreemptively(Duration timeout, Executable executable, String message) { + public static void assertTimeoutPreemptively(Duration timeout, Executable executable, @Nullable String message) { AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, message); } @@ -3497,7 +3580,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeout(Duration, Executable, Supplier) */ public static void assertTimeoutPreemptively(Duration timeout, Executable executable, - Supplier messageSupplier) { + Supplier<@Nullable String> messageSupplier) { AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, executable, messageSupplier); } @@ -3519,7 +3602,8 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable) */ - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { + public static T assertTimeoutPreemptively(Duration timeout, + ThrowingSupplier supplier) { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier); } @@ -3541,7 +3625,8 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier) * @see #assertTimeout(Duration, Executable, String) */ - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, String message) { + public static T assertTimeoutPreemptively(Duration timeout, + ThrowingSupplier supplier, @Nullable String message) { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, message); } @@ -3564,8 +3649,8 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) * @see #assertTimeout(Duration, Executable, Supplier) */ - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier) { + public static T assertTimeoutPreemptively(Duration timeout, + ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); } @@ -3593,8 +3678,9 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * @see #assertTimeout(Duration, Executable, Supplier) */ @API(status = INTERNAL, since = "5.9.1") - public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, - Supplier messageSupplier, TimeoutFailureFactory failureFactory) throws E { + public static T assertTimeoutPreemptively(Duration timeout, + ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier, + TimeoutFailureFactory failureFactory) throws E { return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); } @@ -3610,7 +3696,7 @@ public static T assertTimeoutPreemptively(Duration time * @since 5.8 */ @API(status = STABLE, since = "5.10") - public static T assertInstanceOf(Class expectedType, Object actualValue) { + public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); } @@ -3626,7 +3712,8 @@ public static T assertInstanceOf(Class expectedType, Object actualValue) * @since 5.8 */ @API(status = STABLE, since = "5.10") - public static T assertInstanceOf(Class expectedType, Object actualValue, String message) { + public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, + @Nullable String message) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); } @@ -3643,7 +3730,8 @@ public static T assertInstanceOf(Class expectedType, Object actualValue, * @since 5.8 */ @API(status = STABLE, since = "5.10") - public static T assertInstanceOf(Class expectedType, Object actualValue, Supplier messageSupplier) { + public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, + Supplier<@Nullable String> messageSupplier) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); } @@ -3662,6 +3750,7 @@ public interface TimeoutFailureFactory { * * @return timeout failure; never {@code null} */ - T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause, Thread testThread); + T createTimeoutFailure(Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, + @Nullable Throwable cause, @Nullable Thread testThread); } } 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 6545518b4d3e..b0619ad3cc32 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 @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; @@ -89,7 +90,8 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier) throws TestAbo * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ - public static void assumeTrue(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException { + public static void assumeTrue(BooleanSupplier assumptionSupplier, @Nullable String message) + throws TestAbortedException { assumeTrue(assumptionSupplier.getAsBoolean(), message); } @@ -101,7 +103,8 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, String message * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ - public static void assumeTrue(boolean assumption, Supplier messageSupplier) throws TestAbortedException { + public static void assumeTrue(boolean assumption, Supplier<@Nullable String> messageSupplier) + throws TestAbortedException { if (!assumption) { throwAssumptionFailed(messageSupplier.get()); } @@ -115,7 +118,7 @@ public static void assumeTrue(boolean assumption, Supplier messageSuppli * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ - public static void assumeTrue(boolean assumption, String message) throws TestAbortedException { + public static void assumeTrue(boolean assumption, @Nullable String message) throws TestAbortedException { if (!assumption) { throwAssumptionFailed(message); } @@ -129,7 +132,7 @@ public static void assumeTrue(boolean assumption, String message) throws TestAbo * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ - public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier messageSupplier) + public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { assumeTrue(assumptionSupplier.getAsBoolean(), messageSupplier); @@ -165,7 +168,8 @@ public static void assumeFalse(BooleanSupplier assumptionSupplier) throws TestAb * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ - public static void assumeFalse(BooleanSupplier assumptionSupplier, String message) throws TestAbortedException { + public static void assumeFalse(BooleanSupplier assumptionSupplier, @Nullable String message) + throws TestAbortedException { assumeFalse(assumptionSupplier.getAsBoolean(), message); } @@ -177,7 +181,8 @@ public static void assumeFalse(BooleanSupplier assumptionSupplier, String messag * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ - public static void assumeFalse(boolean assumption, Supplier messageSupplier) throws TestAbortedException { + public static void assumeFalse(boolean assumption, Supplier<@Nullable String> messageSupplier) + throws TestAbortedException { if (assumption) { throwAssumptionFailed(messageSupplier.get()); } @@ -191,7 +196,7 @@ public static void assumeFalse(boolean assumption, Supplier messageSuppl * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ - public static void assumeFalse(boolean assumption, String message) throws TestAbortedException { + public static void assumeFalse(boolean assumption, @Nullable String message) throws TestAbortedException { if (assumption) { throwAssumptionFailed(message); } @@ -205,7 +210,7 @@ public static void assumeFalse(boolean assumption, String message) throws TestAb * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ - public static void assumeFalse(BooleanSupplier assumptionSupplier, Supplier messageSupplier) + public static void assumeFalse(BooleanSupplier assumptionSupplier, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { assumeFalse(assumptionSupplier.getAsBoolean(), messageSupplier); @@ -316,7 +321,7 @@ public static V abort(Supplier messageSupplier) { throw new TestAbortedException(messageSupplier.get()); } - private static void throwAssumptionFailed(String message) { + 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/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java index 99c4ce49b939..ec6bf3e18ebc 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java @@ -31,6 +31,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; @@ -386,14 +387,13 @@ public String generateDisplayNameForMethod(List> enclosingInstanceTypes private String getSentenceBeginning(Class testClass, List> enclosingInstanceTypes) { Class enclosingClass = enclosingInstanceTypes.isEmpty() ? null : enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1); - boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); String sentenceFragment = findAnnotation(testClass, DisplayName.class)// .map(DisplayName::value)// .map(String::trim)// .orElseGet(() -> getSentenceFragment(testClass)); - if (topLevelTestClass) { + if (enclosingClass == null || isStatic(testClass)) { // top-level class if (sentenceFragment != null) { return sentenceFragment; } @@ -501,7 +501,7 @@ private static Optional findIndicativeSentencesGe return findAnnotation(testClass, IndicativeSentencesGeneration.class, enclosingInstanceTypes); } - private static String getSentenceFragment(AnnotatedElement element) { + private static @Nullable String getSentenceFragment(AnnotatedElement element) { return findAnnotation(element, SentenceFragment.class) // .map(SentenceFragment::value) // .map(sentenceFragment -> { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java index da947b68c778..2bdf44fda25d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -17,6 +17,7 @@ import java.util.stream.StreamSupport; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -84,7 +85,7 @@ public static DynamicContainer dynamicContainer(String displayName, Stream dynamicNodes) { return new DynamicContainer(displayName, testSourceUri, dynamicNodes); @@ -92,7 +93,7 @@ public static DynamicContainer dynamicContainer(String displayName, URI testSour private final Stream children; - private DynamicContainer(String displayName, URI testSourceUri, Stream children) { + private DynamicContainer(String displayName, @Nullable URI testSourceUri, Stream children) { super(displayName, testSourceUri); Preconditions.notNull(children, "children must not be null"); this.children = children; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java index 43574ee853a7..59c990ee8b99 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -16,6 +16,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; @@ -33,9 +34,10 @@ public abstract class DynamicNode { private final String displayName; /** Custom test source {@link URI} associated with this node; potentially {@code null}. */ + @Nullable private final URI testSourceUri; - DynamicNode(String displayName, URI testSourceUri) { + DynamicNode(String displayName, @Nullable URI testSourceUri) { this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); this.testSourceUri = testSourceUri; } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java index d87ae1ab4f71..82e8018cb0ba 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -22,6 +22,7 @@ import java.util.stream.StreamSupport; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.platform.commons.util.Preconditions; @@ -78,7 +79,7 @@ public static DynamicTest dynamicTest(String displayName, Executable executable) * @since 5.3 * @see #stream(Iterator, Function, ThrowingConsumer) */ - public static DynamicTest dynamicTest(String displayName, URI testSourceUri, Executable executable) { + public static DynamicTest dynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) { return new DynamicTest(displayName, testSourceUri, executable); } @@ -290,7 +291,7 @@ public static , E extends Executable> Stream str private final Executable executable; - private DynamicTest(String displayName, URI testSourceUri, Executable executable) { + private DynamicTest(String displayName, @Nullable URI testSourceUri, Executable executable) { super(displayName, testSourceUri); this.executable = Preconditions.notNull(executable, "executable must not be null"); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java index dfeff26354e4..2e3edf94164f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -23,7 +24,7 @@ * @since 5.8 */ @API(status = STABLE, since = "5.8") -public interface Named { +public interface Named { /** * Factory method for creating an instance of {@code Named} based on a @@ -37,10 +38,10 @@ public interface Named { * @return an instance of {@code Named}; never {@code null} * @see #named(String, java.lang.Object) */ - static Named of(String name, T payload) { + static Named of(String name, T payload) { Preconditions.notBlank(name, "name must not be null or blank"); - return new Named() { + return new Named<>() { @Override public String getName() { return name; @@ -73,7 +74,7 @@ public String toString() { * @param the type of the payload * @return an instance of {@code Named}; never {@code null} */ - static Named named(String name, T payload) { + static Named named(String name, T payload) { return of(name, payload); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java index 571ab2067d43..968eca1a4e28 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; @@ -67,7 +68,7 @@ protected ConditionEvaluationResult evaluate(DisabledIfEnvironmentVariable annot * {@link System#getenv(String)}. Can be overridden in a subclass for * testing purposes. */ - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return System.getenv(name); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java index 53dab802e793..1885aa9c8a39 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.util.Preconditions; @@ -66,7 +67,7 @@ protected ConditionEvaluationResult evaluate(EnabledIfEnvironmentVariable annota * {@link System#getenv(String)}. Can be overridden in a subclass for * testing purposes. */ - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return System.getenv(name); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java index 16a468daf4fd..157a75dadd57 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java @@ -20,6 +20,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; @@ -67,7 +68,7 @@ Method getConditionMethod(String fullyQualifiedMethodName, ExtensionContext cont String className = methodParts[0]; String methodName = methodParts[1]; ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass); - Class clazz = ReflectionSupport.tryToLoadClass(className, classLoader).getOrThrow( + Class clazz = ReflectionSupport.tryToLoadClass(className, classLoader).getNonNullOrThrow( cause -> new JUnitException("Could not load class [%s]".formatted(className), cause)); return findMethod(clazz, methodName); } @@ -84,6 +85,11 @@ private boolean invokeConditionMethod(Method method, ExtensionContext context) { () -> "Method [%s] must accept either an ExtensionContext or no arguments".formatted(method)); Object testInstance = context.getTestInstance().orElse(null); + return invokeMethod(method, context, testInstance); + } + + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + private static boolean invokeMethod(Method method, ExtensionContext context, @Nullable Object testInstance) { if (method.getParameterCount() == 0) { return (boolean) ReflectionSupport.invokeMethod(method, testInstance); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java index 49878580bd66..8b6f4a348241 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java @@ -15,6 +15,7 @@ import java.util.Locale; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.StringUtils; @@ -94,23 +95,24 @@ public enum OS { private static final Logger logger = LoggerFactory.getLogger(OS.class); + @Nullable private static final OS CURRENT_OS = determineCurrentOs(); /** - * Get the current operating system. + * {@return the current operating system, if known; otherwise, {@code null}} * * @since 5.9 */ @API(status = STABLE, since = "5.10") - public static OS current() { + public static @Nullable OS current() { return CURRENT_OS; } - private static OS determineCurrentOs() { + private static @Nullable OS determineCurrentOs() { return parse(System.getProperty("os.name")); } - static OS parse(String osName) { + static @Nullable OS parse(String osName) { if (StringUtils.isBlank(osName)) { logger.debug( () -> "JVM system property 'os.name' is undefined. It is therefore not possible to detect the current OS."); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java index 14d1f7fb2a80..77ddff4c5100 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/package-info.java @@ -2,4 +2,7 @@ * Annotation-based conditions for enabling or disabling tests in JUnit Jupiter. */ +@NullMarked package org.junit.jupiter.api.condition; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java index 37645f08e1ee..6defd08748ac 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * {@code ExecutableInvoker} allows invoking methods and constructors @@ -33,7 +34,7 @@ public interface ExecutableInvoker { * @param method the method to invoke and resolve parameters for * @see #invoke(Method, Object) */ - default Object invoke(Method method) { + default @Nullable Object invoke(Method method) { return invoke(method, null); } @@ -44,7 +45,8 @@ default Object invoke(Method method) { * @param target the target on which the executable will be invoked; * can be {@code null} for {@code static} methods */ - Object invoke(Method method, Object target); + @Nullable + Object invoke(Method method, @Nullable Object target); /** * Invoke the supplied top-level constructor with dynamic parameter resolution. @@ -67,6 +69,6 @@ default T invoke(Constructor constructor) { * to the constructor; must be {@code null} for top-level classes * or {@code static} nested classes */ - T invoke(Constructor constructor, Object outerInstance); + T invoke(Constructor constructor, @Nullable Object outerInstance); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java index 1f1eeda903f4..e8e5133baa9e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -29,6 +29,7 @@ import java.util.function.Function; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -535,6 +536,7 @@ interface CloseableResource { * @see #get(Object, Class) * @see #getOrDefault(Object, Class, Object) */ + @Nullable Object get(Object key); /** @@ -553,7 +555,7 @@ interface CloseableResource { * @see #get(Object) * @see #getOrDefault(Object, Class, Object) */ - V get(Object key, Class requiredType); + @Nullable V get(Object key, Class requiredType); /** * Get the value of the specified required type that is stored under @@ -570,7 +572,8 @@ interface CloseableResource { * @param requiredType the required type of the value; never {@code null} * @param defaultValue the default value * @param the value type - * @return the value; potentially {@code null} + * @return the value; potentially {@code null} if {@code defaultValue} + * is {@code null} * @since 5.5 * @see #get(Object, Class) */ @@ -613,6 +616,7 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * @see CloseableResource * @see AutoCloseable */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @API(status = STABLE, since = "5.1") default V getOrComputeIfAbsent(Class type) { return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); @@ -648,7 +652,7 @@ default V getOrComputeIfAbsent(Class type) { * @see CloseableResource * @see AutoCloseable */ - Object getOrComputeIfAbsent(K key, Function defaultCreator); + @Nullable Object getOrComputeIfAbsent(K key, Function defaultCreator); /** * Get the value of the specified required type that is stored under the @@ -679,7 +683,7 @@ default V getOrComputeIfAbsent(Class type) { * @see CloseableResource * @see AutoCloseable */ - V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); + @Nullable V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); /** * Store a {@code value} for later retrieval under the supplied {@code key}. @@ -700,7 +704,7 @@ default V getOrComputeIfAbsent(Class type) { * @see CloseableResource * @see AutoCloseable */ - void put(Object key, Object value); + void put(Object key, @Nullable Object value); /** * Remove the value that was previously stored under the supplied {@code key}. @@ -717,6 +721,7 @@ default V getOrComputeIfAbsent(Class type) { * for the specified key * @see #remove(Object, Class) */ + @Nullable Object remove(Object key); /** @@ -734,7 +739,7 @@ default V getOrComputeIfAbsent(Class type) { * for the specified key * @see #remove(Object) */ - V remove(Object key, Class requiredType); + @Nullable V remove(Object key, Class requiredType); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java index eb9ad1138504..0b1f08fa6071 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java @@ -16,6 +16,7 @@ import java.io.Serial; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.platform.commons.JUnitException; @@ -32,12 +33,12 @@ public class ExtensionContextException extends JUnitException { private static final long serialVersionUID = 1L; @SuppressWarnings("unused") - public ExtensionContextException(String message) { + public ExtensionContextException(@Nullable String message) { super(message); } @API(status = EXPERIMENTAL, since = "5.10") - public ExtensionContextException(String message, Throwable cause) { + public ExtensionContextException(@Nullable String message, Throwable cause) { super(message, cause); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java index 6f59a125b15e..89ee85a7735d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -93,7 +94,7 @@ default T interceptTestClassConstructor(Invocation invocation, * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ - default void interceptBeforeAllMethod(Invocation invocation, + default void interceptBeforeAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -108,7 +109,7 @@ default void interceptBeforeAllMethod(Invocation invocation, * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ - default void interceptBeforeEachMethod(Invocation invocation, + default void interceptBeforeEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -123,8 +124,8 @@ default void interceptBeforeEachMethod(Invocation invocation, * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ - default void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { + default void interceptTestMethod(Invocation<@Nullable Void> invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -142,7 +143,7 @@ default void interceptTestMethod(Invocation invocation, ReflectiveInvocati * @return the result of the invocation; potentially {@code null} * @throws Throwable in case of failures */ - default T interceptTestFactoryMethod(Invocation invocation, + default T interceptTestFactoryMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { return invocation.proceed(); } @@ -157,7 +158,7 @@ default T interceptTestFactoryMethod(Invocation invocation, * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ - default void interceptTestTemplateMethod(Invocation invocation, + default void interceptTestTemplateMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -173,8 +174,8 @@ default void interceptTestTemplateMethod(Invocation invocation, * @throws Throwable in case of failures */ @API(status = STABLE, since = "5.11") - default void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, - ExtensionContext extensionContext) throws Throwable { + default void interceptDynamicTest(Invocation<@Nullable Void> invocation, + DynamicTestInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -188,7 +189,7 @@ default void interceptDynamicTest(Invocation invocation, DynamicTestInvoca * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ - default void interceptAfterEachMethod(Invocation invocation, + default void interceptAfterEachMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -203,7 +204,7 @@ default void interceptAfterEachMethod(Invocation invocation, * @param extensionContext the current extension context; never {@code null} * @throws Throwable in case of failures */ - default void interceptAfterAllMethod(Invocation invocation, + default void interceptAfterAllMethod(Invocation<@Nullable Void> invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { invocation.proceed(); } @@ -217,7 +218,7 @@ default void interceptAfterAllMethod(Invocation invocation, * @since 5.5 */ @API(status = STABLE, since = "5.10") - interface Invocation { + interface Invocation { /** * Proceed with this invocation. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java index 093fe276bcf5..898f73a94760 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java @@ -15,6 +15,7 @@ import java.lang.reflect.Parameter; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance; /** @@ -99,6 +100,7 @@ boolean supportsParameter(ParameterContext parameterContext, ExtensionContext ex * @see #supportsParameter * @see ParameterContext */ + @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java index 54c7c24da942..4c4c129e320b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * {@code TestWatcher} defines the API for {@link Extension Extensions} that @@ -103,7 +104,7 @@ default void testSuccessful(ExtensionContext context) { * @param context the current extension context; never {@code null} * @param cause the throwable responsible for the test being aborted; may be {@code null} */ - default void testAborted(ExtensionContext context, Throwable cause) { + default void testAborted(ExtensionContext context, @Nullable Throwable cause) { /* no-op */ } @@ -116,7 +117,7 @@ default void testAborted(ExtensionContext context, Throwable cause) { * @param context the current extension context; never {@code null} * @param cause the throwable that caused test failure; may be {@code null} */ - default void testFailed(ExtensionContext context, Throwable cause) { + default void testFailed(ExtensionContext context, @Nullable Throwable cause) { /* no-op */ } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java index 68f070a2fb4d..940def614dd4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/package-info.java @@ -2,4 +2,7 @@ * JUnit Jupiter API for writing extensions. */ +@NullMarked package org.junit.jupiter.api.extension; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java index f2bcf2f73a50..be80ed5c3a0a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java @@ -16,6 +16,7 @@ import java.lang.reflect.Type; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; @@ -29,7 +30,7 @@ * @since 5.6 */ @API(status = STABLE, since = "5.10") -public abstract class TypeBasedParameterResolver implements ParameterResolver { +public abstract class TypeBasedParameterResolver implements ParameterResolver { private final Type supportedParameterType; @@ -51,8 +52,8 @@ private Type getParameterType(ParameterContext parameterContext) { } private Type enclosedTypeOfParameterResolver() { - ParameterizedType typeBasedParameterResolverSuperclass = findTypeBasedParameterResolverSuperclass(getClass()); - Preconditions.notNull(typeBasedParameterResolverSuperclass, + ParameterizedType typeBasedParameterResolverSuperclass = Preconditions.notNull( + findTypeBasedParameterResolverSuperclass(getClass()), () -> String.format( "Failed to discover parameter type supported by %s; " + "potentially caused by lacking parameterized type in class declaration.", @@ -60,7 +61,7 @@ private Type enclosedTypeOfParameterResolver() { return typeBasedParameterResolverSuperclass.getActualTypeArguments()[0]; } - private ParameterizedType findTypeBasedParameterResolverSuperclass(Class clazz) { + private @Nullable ParameterizedType findTypeBasedParameterResolverSuperclass(Class clazz) { Class superclass = clazz.getSuperclass(); // Abort? diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java index 3d4156a34d6b..897de7983f57 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/package-info.java @@ -2,4 +2,7 @@ * JUnit Jupiter API support for writing extensions. */ +@NullMarked package org.junit.jupiter.api.extension.support; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java index ca32cf145b02..f9a897126f2d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * {@code ThrowingConsumer} is a functional interface that can be used to @@ -41,7 +42,7 @@ */ @FunctionalInterface @API(status = STABLE, since = "5.0") -public interface ThrowingConsumer { +public interface ThrowingConsumer { /** * Consume the supplied argument, potentially throwing an exception. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java index 42212221844a..bfa2c4d87b55 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * {@code ThrowingSupplier} is a functional interface that can be used to @@ -42,7 +43,7 @@ */ @FunctionalInterface @API(status = STABLE, since = "5.0") -public interface ThrowingSupplier { +public interface ThrowingSupplier { /** * Get a result, potentially throwing an exception. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java index aaebdbdb3aab..b70c3db4db85 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/package-info.java @@ -2,4 +2,7 @@ * Functional interfaces used within JUnit Jupiter. */ +@NullMarked package org.junit.jupiter.api.function; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java index 7e8938932e9b..34a9dc7ef6bf 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/package-info.java @@ -2,4 +2,7 @@ * IO-related support in JUnit Jupiter. */ +@NullMarked package org.junit.jupiter.api.io; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java index 38e623dde4aa..7b3888e95e0b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/package-info.java @@ -2,4 +2,7 @@ * JUnit Jupiter API for writing tests. */ +@NullMarked package org.junit.jupiter.api; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java index 16b34edfb116..3969d01029d5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/package-info.java @@ -2,4 +2,7 @@ * JUnit Jupiter API for influencing parallel test execution. */ +@NullMarked package org.junit.jupiter.api.parallel; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts index 24937664e13b..7b3e9afcd3e7 100644 --- a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts +++ b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") `java-test-fixtures` } @@ -11,6 +12,7 @@ dependencies { api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) osgiVerification(projects.junitPlatformLauncher) } diff --git a/junit-jupiter-engine/src/main/java/module-info.java b/junit-jupiter-engine/src/main/java/module-info.java index e8e188aad477..ac95e98aeb2f 100644 --- a/junit-jupiter-engine/src/main/java/module-info.java +++ b/junit-jupiter-engine/src/main/java/module-info.java @@ -18,7 +18,10 @@ * runs Jupiter based tests on the platform. */ module org.junit.jupiter.engine { + requires static org.apiguardian.api; + requires static org.jspecify; + requires org.junit.jupiter.api; requires org.junit.platform.commons; requires org.junit.platform.engine; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java index ede680886ac3..1ce02462c321 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/package-info.java @@ -2,4 +2,7 @@ * Configuration specific to the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine.config; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java index 7adb978fea33..8a9129bc5136 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; @@ -54,6 +55,7 @@ abstract class AbstractExtensionContext implements Ext private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExtensionContext.class); + @Nullable private final ExtensionContext parent; private final EngineExecutionListener engineExecutionListener; private final T testDescriptor; @@ -64,8 +66,8 @@ abstract class AbstractExtensionContext implements Ext private final LauncherStoreFacade launcherStoreFacade; private final NamespacedHierarchicalStore valuesStore; - AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, - JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, + AbstractExtensionContext(@Nullable ExtensionContext parent, EngineExecutionListener engineExecutionListener, + T testDescriptor, JupiterConfiguration configuration, ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade) { Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); @@ -109,7 +111,7 @@ private NamespacedHierarchicalStore.CloseAction createStore( - ExtensionContext parent, LauncherStoreFacade launcherStoreFacade, + @Nullable ExtensionContext parent, LauncherStoreFacade launcherStoreFacade, NamespacedHierarchicalStore.CloseAction closeAction) { NamespacedHierarchicalStore parentStore; if (parent == null) { @@ -172,7 +174,8 @@ private void publishFileEntry(String name, ThrowingConsumer action, Function fileEntryCreator) { Path dir = createOutputDirectory(); Path path = dir.resolve(name); - Preconditions.condition(path.getParent().equals(dir), () -> "name must not contain path separators: " + name); + Preconditions.condition(path.getParent() != null && path.getParent().equals(dir), + () -> "name must not contain path separators: " + name); try { action.accept(path); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java index 09b0dd981361..5151d5c686a7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.descriptor; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.CallbackSupport.invokeAfterCallbacks; @@ -37,6 +38,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -92,7 +94,10 @@ public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor protected final ClassInfo classInfo; + @Nullable private LifecycleMethods lifecycleMethods; + + @Nullable private TestInstanceFactory testInstanceFactory; ClassBasedTestDescriptor(UniqueId uniqueId, Class testClass, Supplier displayNameSupplier, @@ -148,7 +153,7 @@ private void validateDisplayNameAnnotation(DiscoveryIssueReporter reporter) { } protected void validateCoreLifecycleMethods(DiscoveryIssueReporter reporter) { - Validatable.reportAndClear(this.lifecycleMethods.discoveryIssues, reporter); + Validatable.reportAndClear(requireLifecycleMethods().discoveryIssues, reporter); } protected void validateClassTemplateInvocationLifecycleMethods(DiscoveryIssueReporter reporter) { @@ -198,7 +203,8 @@ public final JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext registerExtensionsFromConstructorParameters(registry, getTestClass()); } - this.lifecycleMethods.beforeAll.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); + requireLifecycleMethods().beforeAll.forEach( + method -> registerExtensionsFromExecutableParameters(registry, method)); // Since registerBeforeEachMethodAdapters() and registerAfterEachMethodAdapters() also // invoke registerExtensionsFromExecutableParameters(), we invoke those methods before // invoking registerExtensionsFromExecutableParameters() for @AfterAll methods, @@ -206,7 +212,8 @@ public final JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext // on parameters in lifecycle methods. registerBeforeEachMethodAdapters(registry); registerAfterEachMethodAdapters(registry); - this.lifecycleMethods.afterAll.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); + requireLifecycleMethods().afterAll.forEach( + method -> registerExtensionsFromExecutableParameters(registry, method)); registerExtensionsFromInstanceFields(registry, getTestClass()); ThrowableCollector throwableCollector = createThrowableCollector(); @@ -288,7 +295,7 @@ public void cleanUp(JupiterEngineExecutionContext context) throws Exception { this.testInstanceFactory = null; } - private TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) { + private @Nullable TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registry) { List factories = registry.getExtensions(TestInstanceFactory.class); if (factories.size() == 1) { @@ -347,19 +354,19 @@ protected final TestInstances instantiateTestClass(Optional outer invokeTestInstancePreConstructCallbacks(new DefaultTestInstanceFactoryContext(getTestClass(), outerInstance), registry, extensionContext); Object instance = this.testInstanceFactory != null // - ? invokeTestInstanceFactory(outerInstance, extensionContext) // + ? invokeTestInstanceFactory(this.testInstanceFactory, outerInstance, extensionContext) // : invokeTestClassConstructor(outerInstance, registry, extensionContext); return outerInstances.map(instances -> DefaultTestInstances.of(instances, instance)) // .orElse(DefaultTestInstances.of(instance)); } - private Object invokeTestInstanceFactory(Optional outerInstance, + private Object invokeTestInstanceFactory(TestInstanceFactory testInstanceFactory, Optional outerInstance, ExtensionContextSupplier extensionContext) { Object instance; try { - ExtensionContext actualExtensionContext = extensionContext.get(this.testInstanceFactory); - instance = this.testInstanceFactory.createTestInstance( + ExtensionContext actualExtensionContext = extensionContext.get(testInstanceFactory); + instance = testInstanceFactory.createTestInstance( new DefaultTestInstanceFactoryContext(getTestClass(), outerInstance), actualExtensionContext); } catch (Throwable throwable) { @@ -370,7 +377,7 @@ private Object invokeTestInstanceFactory(Optional outerInstance, } String message = "TestInstanceFactory [%s] failed to instantiate test class [%s]".formatted( - this.testInstanceFactory.getClass().getName(), getTestClass().getName()); + testInstanceFactory.getClass().getName(), getTestClass().getName()); if (StringUtils.isNotBlank(throwable.getMessage())) { message += ": " + throwable.getMessage(); } @@ -390,7 +397,7 @@ private Object invokeTestInstanceFactory(Optional outerInstance, instanceClassName += "@" + Integer.toHexString(System.identityHashCode(instanceClass)); } String message = "TestInstanceFactory [%s] failed to return an instance of [%s] and instead returned an instance of [%s].".formatted( - this.testInstanceFactory.getClass().getName(), testClassName, instanceClassName); + testInstanceFactory.getClass().getName(), testClassName, instanceClassName); throw new TestInstantiationException(message); } @@ -438,7 +445,7 @@ private void invokeBeforeAllMethods(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); Object testInstance = extensionContext.getTestInstance().orElse(null); - for (Method method : this.lifecycleMethods.beforeAll) { + for (Method method : requireLifecycleMethods().beforeAll) { throwableCollector.execute(() -> { try { executableInvoker.invoke(method, testInstance, extensionContext, registry, @@ -467,7 +474,7 @@ private void invokeAfterAllMethods(JupiterEngineExecutionContext context) { ThrowableCollector throwableCollector = context.getThrowableCollector(); Object testInstance = extensionContext.getTestInstance().orElse(null); - this.lifecycleMethods.afterAll.forEach(method -> throwableCollector.execute(() -> { + requireLifecycleMethods().afterAll.forEach(method -> throwableCollector.execute(() -> { try { executableInvoker.invoke(method, testInstance, extensionContext, registry, ReflectiveInterceptorCall.ofVoidMethod(InvocationInterceptor::interceptAfterAllMethod)); @@ -500,13 +507,13 @@ private boolean isPerClassLifecycle(JupiterEngineExecutionContext context) { } private void registerBeforeEachMethodAdapters(ExtensionRegistrar registrar) { - registerMethodsAsExtensions(this.lifecycleMethods.beforeEach, registrar, + registerMethodsAsExtensions(requireLifecycleMethods().beforeEach, registrar, this::synthesizeBeforeEachMethodAdapter); } private void registerAfterEachMethodAdapters(ExtensionRegistrar registrar) { // Make a local copy since findAfterEachMethods() returns an immutable list. - List afterEachMethods = new ArrayList<>(this.lifecycleMethods.afterEach); + List afterEachMethods = new ArrayList<>(requireLifecycleMethods().afterEach); // Since the bottom-up ordering of afterEachMethods will later be reversed when the // synthesized AfterEachMethodAdapters are executed within TestMethodTestDescriptor, @@ -546,6 +553,10 @@ private void invokeMethodInExtensionContext(Method method, ExtensionContext cont ReflectiveInterceptorCall.ofVoidMethod(interceptorCall)); } + private LifecycleMethods requireLifecycleMethods() { + return requireNonNull(this.lifecycleMethods); + } + protected static class ClassInfo { private final List discoveryIssues = new ArrayList<>(); @@ -553,7 +564,10 @@ protected static class ClassInfo { final Class testClass; final Set tags; final Lifecycle lifecycle; + + @Nullable ExecutionMode defaultChildExecutionMode; + final ExclusiveResourceCollector exclusiveResourceCollector; ClassInfo(Class testClass, JupiterConfiguration configuration) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java index cc052d0a60f5..157ae5ccb84c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; @@ -33,6 +34,7 @@ final class ClassExtensionContext extends AbstractExtensionContext uniqueIdTransformer) { return new ClassTemplateInvocationTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), parent, - this.invocationContext, this.index, getSource().orElse(null), this.configuration); + requiredInvocationContext(), this.index, getSource().orElse(null), this.configuration); } // --- TestDescriptor ------------------------------------------------------ @@ -111,18 +116,18 @@ public Function> getResou @Override public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = context.getExtensionRegistry(); - List additionalExtensions = this.invocationContext.getAdditionalExtensions(); + List additionalExtensions = requiredInvocationContext().getAdditionalExtensions(); if (!additionalExtensions.isEmpty()) { MutableExtensionRegistry childRegistry = createRegistryFrom(registry, Stream.empty()); additionalExtensions.forEach( - extension -> childRegistry.registerExtension(extension, this.invocationContext)); + extension -> childRegistry.registerExtension(extension, requiredInvocationContext())); registry = childRegistry; } ExtensionContext extensionContext = new ClassTemplateInvocationExtensionContext(context.getExtensionContext(), context.getExecutionListener(), this, context.getConfiguration(), registry, context.getLauncherStoreFacade()); ThrowableCollector throwableCollector = createThrowableCollector(); - throwableCollector.execute(() -> this.invocationContext.prepareInvocation(extensionContext)); + throwableCollector.execute(() -> requiredInvocationContext().prepareInvocation(extensionContext)); return context.extend() // .withExtensionRegistry(registry) // .withExtensionContext(extensionContext) // @@ -177,4 +182,8 @@ public void cleanUp(JupiterEngineExecutionContext context) throws Exception { this.invocationContext = null; super.cleanUp(context); } + + private ClassTemplateInvocationContext requiredInvocationContext() { + return requireNonNull(this.invocationContext); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java index a25d2e16b6ee..edf8a8d117d0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java @@ -22,6 +22,7 @@ import java.util.function.BiFunction; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -82,7 +83,7 @@ static String determineDisplayName(AnnotatedElement element, Supplier di } static void validateAnnotation(AnnotatedElement element, Supplier elementDescription, - Supplier sourceProvider, DiscoveryIssueReporter reporter) { + Supplier<@Nullable TestSource> sourceProvider, DiscoveryIssueReporter reporter) { findAnnotation(element, DisplayName.class) // .map(DisplayName::value) // .filter(StringUtils::isBlank) // diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java index 865e4044f52b..f68c98b480bf 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicNodeTestDescriptor.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.descriptor; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; @@ -27,7 +28,7 @@ abstract class DynamicNodeTestDescriptor extends JupiterTestDescriptor { protected final int index; - DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, TestSource testSource, + DynamicNodeTestDescriptor(UniqueId uniqueId, int index, DynamicNode dynamicNode, @Nullable TestSource testSource, JupiterConfiguration configuration) { super(uniqueId, dynamicNode.getDisplayName(), testSource, configuration); this.index = index; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java index 9209b8c1f3d6..b45aefc619fd 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicTestTestDescriptor.java @@ -10,8 +10,11 @@ package org.junit.jupiter.engine.descriptor; +import static java.util.Objects.requireNonNull; + import java.util.function.UnaryOperator; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.extension.ExtensionContext; @@ -35,9 +38,10 @@ class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { private static final InvocationInterceptorChain interceptorChain = new InvocationInterceptorChain(); + @Nullable private DynamicTest dynamicTest; - DynamicTestTestDescriptor(UniqueId uniqueId, int index, DynamicTest dynamicTest, TestSource source, + DynamicTestTestDescriptor(UniqueId uniqueId, int index, DynamicTest dynamicTest, @Nullable TestSource source, JupiterConfiguration configuration) { super(uniqueId, index, dynamicTest, source, configuration); this.dynamicTest = dynamicTest; @@ -45,8 +49,8 @@ class DynamicTestTestDescriptor extends DynamicNodeTestDescriptor { @Override protected DynamicTestTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { - return new DynamicTestTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.index, this.dynamicTest, - this.getSource().orElse(null), this.configuration); + return new DynamicTestTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), this.index, + requireNonNull(this.dynamicTest), this.getSource().orElse(null), this.configuration); } @Override @@ -57,20 +61,24 @@ public Type getType() { @Override public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext context, DynamicTestExecutor dynamicTestExecutor) { - InvocationInterceptor.Invocation invocation = () -> { - dynamicTest.getExecutable().execute(); - return null; - }; DynamicTestInvocationContext dynamicTestInvocationContext = new DefaultDynamicTestInvocationContext( - dynamicTest.getExecutable()); + requiredDynamicTest().getExecutable()); ExtensionContext extensionContext = context.getExtensionContext(); ExtensionRegistry extensionRegistry = context.getExtensionRegistry(); - interceptorChain.invoke(invocation, extensionRegistry, InterceptorCall.ofVoid( + interceptorChain.invoke(toInvocation(), extensionRegistry, InterceptorCall.ofVoid( (interceptor, wrappedInvocation) -> interceptor.interceptDynamicTest(wrappedInvocation, dynamicTestInvocationContext, extensionContext))); return context; } + @SuppressWarnings("NullAway") + private InvocationInterceptor.Invocation toInvocation() { + return () -> { + requiredDynamicTest().getExecutable().execute(); + return null; + }; + } + /** * Avoid an {@link OutOfMemoryError} by releasing the reference to this * descriptor's {@link DynamicTest} which holds a reference to the user-supplied @@ -86,4 +94,8 @@ public void after(JupiterEngineExecutionContext context) throws Exception { this.dynamicTest = null; } + private DynamicTest requiredDynamicTest() { + return requireNonNull(dynamicTest); + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java index 15e7d26b6db5..9e3dde197bbe 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExclusiveResourceCollector.java @@ -21,6 +21,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.api.parallel.ResourceLockTarget; @@ -73,6 +74,8 @@ static ExclusiveResourceCollector from(AnnotatedElement element) { private static class DefaultExclusiveResourceCollector extends ExclusiveResourceCollector { private final List annotations; + + @Nullable private List providers; DefaultExclusiveResourceCollector(List annotations) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java index 65845e8ecca6..4ad96e1818db 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java @@ -29,6 +29,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; @@ -138,7 +139,7 @@ static void registerExtensionsFromInstanceFields(ExtensionRegistrar registrar, C /** * @since 5.11 */ - private static Extension readAndValidateExtensionFromField(Field field, Object instance, + private static Extension readAndValidateExtensionFromField(Field field, @Nullable Object instance, List> declarativeExtensionTypes) { Object value = tryToReadFieldValue(field, instance) // .getOrThrow(e -> new PreconditionViolationException( diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java index fe5547cafd84..893234e8f017 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptor.java @@ -31,6 +31,7 @@ import java.util.function.UnaryOperator; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.Extension; @@ -65,11 +66,11 @@ public abstract class JupiterTestDescriptor extends AbstractTestDescriptor final JupiterConfiguration configuration; JupiterTestDescriptor(UniqueId uniqueId, AnnotatedElement element, Supplier displayNameSupplier, - TestSource source, JupiterConfiguration configuration) { + @Nullable TestSource source, JupiterConfiguration configuration) { this(uniqueId, determineDisplayName(element, displayNameSupplier), source, configuration); } - JupiterTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, + JupiterTestDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source, JupiterConfiguration configuration) { super(uniqueId, displayName, source); this.configuration = configuration; @@ -79,7 +80,7 @@ public abstract class JupiterTestDescriptor extends AbstractTestDescriptor static Set getTags(AnnotatedElement element, Supplier elementDescription, Supplier sourceProvider, Consumer issueCollector) { - AtomicReference source = new AtomicReference<>(); + AtomicReference<@Nullable TestSource> source = new AtomicReference<>(); return findRepeatableAnnotations(element, Tag.class).stream() // .map(Tag::value) // .filter(tag -> { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index c4d9af414bee..ae07732fdf70 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -156,10 +156,8 @@ protected Optional getExplicitExecutionMode() { */ @Override public void nodeSkipped(JupiterEngineExecutionContext context, TestDescriptor descriptor, SkipResult result) { - if (context != null) { - invokeTestWatchers(context, false, - watcher -> watcher.testDisabled(context.getExtensionContext(), result.getReason())); - } + invokeTestWatchers(context, false, + watcher -> watcher.testDisabled(context.getExtensionContext(), result.getReason())); } /** diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java index eaed0fe9418a..9f6711e0ea4b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; @@ -31,6 +32,7 @@ final class MethodExtensionContext extends AbstractExtensionContext> getEnclosingTestClasses() { } @API(status = INTERNAL, since = "5.12") - public static List> getEnclosingTestClasses(TestDescriptor parent) { + public static List> getEnclosingTestClasses(@Nullable TestDescriptor parent) { if (parent instanceof TestClassAware parentClassDescriptor) { List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); result.add(parentClassDescriptor.getTestClass()); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java index c66398105f4b..47c1cf12bf6b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -23,6 +23,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.hierarchical.ExclusiveResource; @@ -71,8 +72,9 @@ default Stream determineOwnExclusiveResources( static Function> enclosingInstanceTypesDependentResourceLocksProviderEvaluator( Supplier>> enclosingInstanceTypesSupplier, BiFunction>, Set> evaluator) { - return new Function>() { + return new Function<>() { + @Nullable private List> enclosingInstanceTypes; @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index a2d782bad1fa..7b50ac238292 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -25,6 +25,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; @@ -58,7 +59,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen public static final String DYNAMIC_CONTAINER_SEGMENT_TYPE = "dynamic-container"; public static final String DYNAMIC_TEST_SEGMENT_TYPE = "dynamic-test"; - private static final ReflectiveInterceptorCall interceptorCall = InvocationInterceptor::interceptTestFactoryMethod; + @SuppressWarnings("NullAway") + private static final ReflectiveInterceptorCall interceptorCall = InvocationInterceptor::interceptTestFactoryMethod; private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); private final DynamicDescendantFilter dynamicDescendantFilter; @@ -133,7 +135,10 @@ protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTe } @SuppressWarnings("unchecked") - private Stream toDynamicNodeStream(Object testFactoryMethodResult) { + private Stream toDynamicNodeStream(@Nullable Object testFactoryMethodResult) { + if (testFactoryMethodResult == null) { + throw new JUnitException("@TestFactory method must not return null"); + } if (testFactoryMethodResult instanceof DynamicNode node) { return Stream.of(node); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index c6a416bd443a..bcf5a5fc21d7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -280,24 +280,22 @@ private void invokeTestInstancePreDestroyCallbacks(JupiterEngineExecutionContext public void nodeFinished(JupiterEngineExecutionContext context, TestDescriptor descriptor, TestExecutionResult result) { - if (context != null) { - ExtensionContext extensionContext = context.getExtensionContext(); - TestExecutionResult.Status status = result.getStatus(); - - invokeTestWatchers(context, true, watcher -> { - switch (status) { - case SUCCESSFUL: - watcher.testSuccessful(extensionContext); - break; - case ABORTED: - watcher.testAborted(extensionContext, result.getThrowable().orElse(null)); - break; - case FAILED: - watcher.testFailed(extensionContext, result.getThrowable().orElse(null)); - break; - } - }); - } + ExtensionContext extensionContext = context.getExtensionContext(); + TestExecutionResult.Status status = result.getStatus(); + + invokeTestWatchers(context, true, watcher -> { + switch (status) { + case SUCCESSFUL: + watcher.testSuccessful(extensionContext); + break; + case ABORTED: + watcher.testAborted(extensionContext, result.getThrowable().orElse(null)); + break; + case FAILED: + watcher.testFailed(extensionContext, result.getThrowable().orElse(null)); + break; + } + }); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java index 9a30e567ba2b..1a947e808d74 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; @@ -28,11 +29,13 @@ */ final class TestTemplateExtensionContext extends AbstractExtensionContext { + @Nullable private final TestInstances testInstances; TestTemplateExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, TestTemplateTestDescriptor testDescriptor, JupiterConfiguration configuration, - ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade, TestInstances testInstances) { + ExtensionRegistry extensionRegistry, LauncherStoreFacade launcherStoreFacade, + @Nullable TestInstances testInstances) { super(parent, engineExecutionListener, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); this.testInstances = testInstances; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java index 88d494a9731d..d768a62cfa97 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptor.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.descriptor; import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import java.lang.reflect.Method; @@ -18,6 +19,7 @@ import java.util.function.UnaryOperator; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; @@ -42,7 +44,9 @@ public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescript private static final ReflectiveInterceptorCall interceptorCall = ReflectiveInterceptorCall.ofVoidMethod( InvocationInterceptor::interceptTestTemplateMethod); + @Nullable private TestTemplateInvocationContext invocationContext; + private final int index; TestTemplateInvocationTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, @@ -58,7 +62,7 @@ public class TestTemplateInvocationTestDescriptor extends TestMethodTestDescript @Override protected TestTemplateInvocationTestDescriptor withUniqueId(UnaryOperator uniqueIdTransformer) { return new TestTemplateInvocationTestDescriptor(uniqueIdTransformer.apply(getUniqueId()), getTestClass(), - getTestMethod(), this.invocationContext, this.index, this.configuration); + getTestMethod(), requiredInvocationContext(), this.index, this.configuration); } // --- TestDescriptor ------------------------------------------------------ @@ -77,14 +81,15 @@ public String getLegacyReportingName() { @Override protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = super.populateNewExtensionRegistry(context); - this.invocationContext.getAdditionalExtensions().forEach( + var invocationContext = requiredInvocationContext(); + invocationContext.getAdditionalExtensions().forEach( extension -> registry.registerExtension(extension, invocationContext)); return registry; } @Override protected void prepareExtensionContext(ExtensionContext extensionContext) { - this.invocationContext.prepareInvocation(extensionContext); + requiredInvocationContext().prepareInvocation(extensionContext); } @Override @@ -93,4 +98,8 @@ public void after(JupiterEngineExecutionContext context) { this.invocationContext = null; } + private TestTemplateInvocationContext requiredInvocationContext() { + return requireNonNull(this.invocationContext); + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java index e3e0028ce4fb..62b21c2e4993 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/package-info.java @@ -2,4 +2,7 @@ * Test descriptors used within the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine.descriptor; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java index 8f104923bf5c..fba71e40357a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.discovery; import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; @@ -23,6 +24,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; @@ -137,12 +139,15 @@ protected static DescriptorWrapperOrderer n return (DescriptorWrapperOrderer) NOOP; } + @Nullable private final ORDERER orderer; + @Nullable private final Consumer> orderingAction; + private final MessageGenerator descriptorsAddedMessageGenerator; private final MessageGenerator descriptorsRemovedMessageGenerator; - DescriptorWrapperOrderer(ORDERER orderer, Consumer> orderingAction, + DescriptorWrapperOrderer(@Nullable ORDERER orderer, @Nullable Consumer> orderingAction, MessageGenerator descriptorsAddedMessageGenerator, MessageGenerator descriptorsRemovedMessageGenerator) { @@ -152,6 +157,7 @@ protected static DescriptorWrapperOrderer n this.descriptorsRemovedMessageGenerator = descriptorsRemovedMessageGenerator; } + @Nullable ORDERER getOrderer() { return orderer; } @@ -162,7 +168,7 @@ private boolean canOrderWrappers() { private void orderWrappers(List wrappers, Consumer errorHandler) { List orderedWrappers = new ArrayList<>(wrappers); - this.orderingAction.accept(orderedWrappers); + requireNonNull(this.orderingAction).accept(orderedWrappers); Map distinctWrappersToIndex = distinctWrappersToIndex(orderedWrappers); int difference = orderedWrappers.size() - wrappers.size(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java index 0426627d49ba..d9a5e573aafc 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/package-info.java @@ -3,4 +3,7 @@ * Contains resolvers for Java elements. */ +@NullMarked package org.junit.jupiter.engine.discovery; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java index 579eb486e17d..028f0bbbf8c0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/package-info.java @@ -2,4 +2,7 @@ * Internal predicate classes used by test discovery within the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine.discovery.predicates; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java index 79cbd46c6895..d046535c0a56 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.platform.commons.util.ReflectionUtils; @@ -24,9 +25,9 @@ class ConstructorInvocation implements Invocation, ReflectiveInvocationContext> { private final Constructor constructor; - private final Object[] arguments; + private final @Nullable Object[] arguments; - ConstructorInvocation(Constructor constructor, Object[] arguments) { + ConstructorInvocation(Constructor constructor, @Nullable Object[] arguments) { this.constructor = constructor; this.arguments = arguments; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java index 122c122030eb..37a663ebb0b8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; @@ -39,14 +40,16 @@ public DefaultExecutableInvoker(ExtensionContext extensionContext, ExtensionRegi } @Override - public T invoke(Constructor constructor, Object outerInstance) { + public T invoke(Constructor constructor, @Nullable Object outerInstance) { + @Nullable Object[] arguments = resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), extensionContext, extensionRegistry); return ReflectionUtils.newInstance(constructor, arguments); } @Override - public Object invoke(Method method, Object target) { + public @Nullable Object invoke(Method method, @Nullable Object target) { + @Nullable Object[] arguments = resolveParameters(method, Optional.ofNullable(target), extensionContext, extensionRegistry); return MethodReflectionUtils.invoke(method, target, arguments); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java index 9454d5829302..8dc93f242fa0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java @@ -19,6 +19,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; @@ -57,6 +58,7 @@ public T invoke(Constructor constructor, Optional outerInstance, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall, T> interceptorCall) { + @Nullable Object[] arguments = resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, extensionRegistry); ConstructorInvocation invocation = new ConstructorInvocation<>(constructor, arguments); @@ -76,12 +78,13 @@ public T invoke(Constructor constructor, Optional outerInstance, * @param interceptorCall the call for intercepting this method invocation * via all registered {@linkplain InvocationInterceptor interceptors} */ - public T invoke(Method method, Object target, ExtensionContext extensionContext, + public T invoke(Method method, @Nullable Object target, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall interceptorCall) { @SuppressWarnings("unchecked") Optional optionalTarget = (target instanceof Optional ? (Optional) target : Optional.ofNullable(target)); + @Nullable Object[] arguments = resolveParameters(method, optionalTarget, extensionContext, extensionRegistry); MethodInvocation invocation = new MethodInvocation<>(method, optionalTarget, arguments); return invoke(invocation, invocation, extensionContext, extensionRegistry, interceptorCall); @@ -102,12 +105,13 @@ private T invoke(Invocation originalInvocation, extensionContext.get(interceptor))); } - public interface ReflectiveInterceptorCall { + public interface ReflectiveInterceptorCall { T apply(InvocationInterceptor interceptor, Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable; - static ReflectiveInterceptorCall ofVoidMethod(VoidMethodInterceptorCall call) { + @SuppressWarnings("NullAway") + static ReflectiveInterceptorCall ofVoidMethod(VoidMethodInterceptorCall call) { return ((interceptorChain, invocation, invocationContext, extensionContext) -> { call.apply(interceptorChain, invocation, invocationContext, extensionContext); return null; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java index 3b73cbc982e7..6f2352716f99 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java @@ -18,6 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.engine.extension.ExtensionRegistry; @@ -69,11 +70,12 @@ private T proceed(Invocation invocation) { } @FunctionalInterface - public interface InterceptorCall { + public interface InterceptorCall { T apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; - static InterceptorCall ofVoid(VoidInterceptorCall call) { + @SuppressWarnings("NullAway") + static InterceptorCall<@Nullable Void> ofVoid(VoidInterceptorCall call) { return ((interceptorChain, invocation) -> { call.apply(interceptorChain, invocation); return null; @@ -85,7 +87,7 @@ static InterceptorCall ofVoid(VoidInterceptorCall call) { @FunctionalInterface public interface VoidInterceptorCall { - void apply(InvocationInterceptor interceptor, Invocation invocation) throws Throwable; + void apply(InvocationInterceptor interceptor, Invocation<@Nullable Void> invocation) throws Throwable; } @@ -112,7 +114,7 @@ public void skip() { } } - private static class ValidatingInvocation implements Invocation { + private static class ValidatingInvocation implements Invocation { private static final Logger logger = LoggerFactory.getLogger(ValidatingInvocation.class); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java index f62338b9b272..d09caed6ad1f 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java @@ -10,9 +10,11 @@ package org.junit.jupiter.engine.execution; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.LauncherStoreFacade; @@ -68,19 +70,19 @@ public LauncherStoreFacade getLauncherStoreFacade() { } public TestInstancesProvider getTestInstancesProvider() { - return this.state.testInstancesProvider; + return requireNonNull(this.state.testInstancesProvider); } public MutableExtensionRegistry getExtensionRegistry() { - return this.state.extensionRegistry; + return requireNonNull(this.state.extensionRegistry); } public ExtensionContext getExtensionContext() { - return this.state.extensionContext; + return requireNonNull(this.state.extensionContext); } public ThrowableCollector getThrowableCollector() { - return this.state.throwableCollector; + return requireNonNull(this.state.throwableCollector); } /** @@ -125,9 +127,17 @@ private static final class State implements Cloneable { final EngineExecutionListener executionListener; final JupiterConfiguration configuration; final LauncherStoreFacade launcherStoreFacade; + + @Nullable TestInstancesProvider testInstancesProvider; + + @Nullable MutableExtensionRegistry extensionRegistry; + + @Nullable ExtensionContext extensionContext; + + @Nullable ThrowableCollector throwableCollector; State(EngineExecutionListener executionListener, JupiterConfiguration configuration, @@ -152,6 +162,8 @@ public State clone() { public static class Builder { private State originalState; + + @Nullable private State newState = null; private Builder(State originalState) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java index 5d42c754d1ba..6ef6f52f4bad 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java @@ -17,17 +17,18 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.jupiter.engine.support.MethodReflectionUtils; -class MethodInvocation implements Invocation, ReflectiveInvocationContext { +class MethodInvocation implements Invocation, ReflectiveInvocationContext { private final Method method; private final Optional target; - private final Object[] arguments; + private final @Nullable Object[] arguments; - MethodInvocation(Method method, Optional target, Object[] arguments) { + MethodInvocation(Method method, Optional target, @Nullable Object[] arguments) { this.method = method; this.target = target; this.arguments = arguments; @@ -39,7 +40,6 @@ public Class getTargetClass() { } @Override - @SuppressWarnings("unchecked") public Optional getTarget() { return this.target; } @@ -55,7 +55,7 @@ public List getArguments() { } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "NullAway" }) public T proceed() { var actualTarget = this.target.orElse(null); return (T) MethodReflectionUtils.invoke(this.method, actualTarget, this.arguments); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java index d63c7f13a940..59897452d19d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ExtensionContextException; import org.junit.platform.commons.util.Preconditions; @@ -38,27 +39,27 @@ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N } @Override - public Object get(Object key) { + public @Nullable Object get(Object key) { Preconditions.notNull(key, "key must not be null"); return accessStore(() -> this.valuesStore.get(this.namespace, key)); } @Override - public T get(Object key, Class requiredType) { + public @Nullable T get(Object key, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); return accessStore(() -> this.valuesStore.get(this.namespace, key, requiredType)); } @Override - public Object getOrComputeIfAbsent(K key, Function defaultCreator) { + public @Nullable Object getOrComputeIfAbsent(K key, Function defaultCreator) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); return accessStore(() -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator)); } @Override - public V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { + public @Nullable V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); @@ -67,25 +68,25 @@ public V getOrComputeIfAbsent(K key, Function defaultCreator, Class } @Override - public void put(Object key, Object value) { + public void put(Object key, @Nullable Object value) { Preconditions.notNull(key, "key must not be null"); accessStore(() -> this.valuesStore.put(this.namespace, key, value)); } @Override - public Object remove(Object key) { + public @Nullable Object remove(Object key) { Preconditions.notNull(key, "key must not be null"); return accessStore(() -> this.valuesStore.remove(this.namespace, key)); } @Override - public T remove(Object key, Class requiredType) { + public @Nullable T remove(Object key, Class requiredType) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); return accessStore(() -> this.valuesStore.remove(this.namespace, key, requiredType)); } - private T accessStore(Supplier action) { + private @Nullable T accessStore(Supplier<@Nullable T> action) { try { return action.get(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java index efd12ab21592..c83032af3ac8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java @@ -25,6 +25,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; @@ -60,8 +61,8 @@ public class ParameterResolutionUtils { * @return the array of Objects to be used as parameters in the executable * invocation; never {@code null} though potentially empty */ - public static Object[] resolveParameters(Method method, Optional target, ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry) { + public static @Nullable Object[] resolveParameters(Method method, Optional target, + ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return resolveParameters(method, target, Optional.empty(), __ -> extensionContext, extensionRegistry, isKotlinSuspendingFunction(method) // @@ -86,12 +87,12 @@ public static Object[] resolveParameters(Method method, Optional target, * @return the array of Objects to be used as parameters in the executable * invocation; never {@code null} though potentially empty */ - public static Object[] resolveParameters(Executable executable, Optional target, + public static @Nullable Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { return resolveParameters(executable, target, outerInstance, __ -> extensionContext, extensionRegistry); } - public static Object[] resolveParameters(Executable executable, Optional target, + public static @Nullable Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry) { @@ -99,12 +100,13 @@ public static Object[] resolveParameters(Executable executable, Optional executable.getParameters()); } - private static Object[] resolveParameters(Executable executable, Optional target, + private static @Nullable Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry, Parameter[] parameters) { Preconditions.notNull(target, "target must not be null"); + @Nullable Object[] values = new Object[parameters.length]; int start = 0; @@ -123,7 +125,7 @@ private static Object[] resolveParameters(Executable executable, Optional type = parameter.getType(); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java index 028111e0a0ca..cc5cec268e7d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/package-info.java @@ -2,4 +2,7 @@ * Internal classes for test execution within the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine.execution; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java index b4d4261d772e..c3bf48d99ec9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java @@ -16,6 +16,7 @@ import java.lang.reflect.Method; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.ExtensionConfigurationException; @@ -59,14 +60,15 @@ public void afterAll(ExtensionContext context) { throwableCollector.assertEmpty(); } - private static void closeFields(Class testClass, Object testInstance, ThrowableCollector throwableCollector) { + private static void closeFields(Class testClass, @Nullable Object testInstance, + ThrowableCollector throwableCollector) { Predicate predicate = (testInstance == null ? ModifierSupport::isStatic : ModifierSupport::isNotStatic); AnnotationSupport.findAnnotatedFields(testClass, AutoClose.class, predicate, BOTTOM_UP).forEach( field -> throwableCollector.execute(() -> closeField(field, testInstance))); } - private static void closeField(Field field, Object testInstance) throws Exception { - String methodName = AnnotationSupport.findAnnotation(field, AutoClose.class).get().value(); + private static void closeField(Field field, @Nullable Object testInstance) throws Exception { + String methodName = AnnotationSupport.findAnnotation(field, AutoClose.class).orElseThrow().value(); Class fieldType = field.getType(); checkCondition(StringUtils.isNotBlank(methodName), "@AutoClose on field %s must specify a method name.", field); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java index 5dedc43e9107..9d4e1e4d3727 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java @@ -14,6 +14,7 @@ import java.lang.reflect.AnnotatedElement; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; @@ -44,7 +45,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con .orElse(ENABLED); } - private ConditionEvaluationResult toResult(AnnotatedElement element, Disabled annotation) { + private ConditionEvaluationResult toResult(@Nullable AnnotatedElement element, Disabled annotation) { String value = annotation.value(); String reason = StringUtils.isNotBlank(value) ? value : element + " is @Disabled"; return ConditionEvaluationResult.disabled(reason); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java index 4dd3f44ff778..654d52995004 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java @@ -33,6 +33,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.logging.Logger; @@ -256,7 +257,7 @@ private void registerExtension(String category, Extension extension) { registerExtension(category, extension, null); } - private void registerExtension(String category, Extension extension, Object source) { + private void registerExtension(String category, Extension extension, @Nullable Object source) { Preconditions.notBlank(category, "category must not be null or blank"); Preconditions.notNull(extension, "extension must not be null"); @@ -266,7 +267,7 @@ private void registerExtension(String category, Extension extension, Object sour this.registeredExtensionTypes.add(extension.getClass()); } - private String buildSourceInfo(Object source) { + private String buildSourceInfo(@Nullable Object source) { if (source == null) { return ""; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java index b22d07419534..6eac77602c66 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.RepetitionInfo; import org.junit.jupiter.api.extension.ConditionEvaluationResult; @@ -61,7 +62,7 @@ public RepetitionInfo resolveParameter(ParameterContext parameterContext, Extens } @Override - public void testFailed(ExtensionContext context, Throwable cause) { + public void testFailed(ExtensionContext context, @Nullable Throwable cause) { this.repetitionInfo.failureCount.incrementAndGet(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java index 7a5a78ab1544..395726217fa9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java @@ -16,13 +16,14 @@ import java.util.concurrent.ScheduledFuture; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.platform.commons.util.UnrecoverableExceptions; /** * @since 5.5 */ -class SameThreadTimeoutInvocation implements Invocation { +class SameThreadTimeoutInvocation implements Invocation { private final Invocation delegate; private final TimeoutDuration timeout; @@ -39,6 +40,7 @@ class SameThreadTimeoutInvocation implements Invocation { this.preInterruptCallback = preInterruptCallback; } + @SuppressWarnings("NullAway") @Override public T proceed() throws Throwable { InterruptTask interruptTask = new InterruptTask(Thread.currentThread(), preInterruptCallback); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java index 5500f773720e..d9783d1b7942 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -15,12 +15,13 @@ import java.util.concurrent.TimeoutException; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; /** * @since 5.9 */ -class SeparateThreadTimeoutInvocation implements Invocation { +class SeparateThreadTimeoutInvocation implements Invocation { private final Invocation delegate; private final TimeoutDuration timeout; @@ -39,8 +40,10 @@ class SeparateThreadTimeoutInvocation implements Invocation { public T proceed() throws Throwable { return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, (__, messageSupplier, cause, testThread) -> { - TimeoutException exception = TimeoutExceptionFactory.create(messageSupplier.get(), timeout, null); - preInterruptCallback.executePreInterruptCallback(testThread, exception::addSuppressed); + TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); + if (testThread != null) { + preInterruptCallback.executePreInterruptCallback(testThread, exception::addSuppressed); + } exception.initCause(cause); return exception; }); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java index 5252b18f4f03..3339a17c8f23 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -12,6 +12,7 @@ import static java.nio.file.FileVisitResult.CONTINUE; import static java.nio.file.FileVisitResult.SKIP_SUBTREE; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; @@ -46,6 +47,7 @@ import java.util.TreeMap; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -146,7 +148,7 @@ private void injectInstanceFields(ExtensionContext context, Object instance) { } } - private void injectFields(ExtensionContext context, Object testInstance, Class testClass, + private void injectFields(ExtensionContext context, @Nullable Object testInstance, Class testClass, Predicate predicate) { findAnnotatedFields(testClass, TempDir.class, predicate).forEach(field -> { @@ -240,11 +242,11 @@ private static void assertSupportedType(String target, Class type) { private static Object getPathOrFile(Class elementType, AnnotatedElementContext elementContext, TempDirFactory factory, CleanupMode cleanupMode, ExtensionContext extensionContext) { - Path path = extensionContext.getStore(NAMESPACE.append(elementContext)) // + Path path = requireNonNull(extensionContext.getStore(NAMESPACE.append(elementContext)) // .getOrComputeIfAbsent(KEY, __ -> createTempDir(factory, cleanupMode, elementType, elementContext, extensionContext), - CloseablePath.class) // - .get(); + CloseablePath.class)) // + .get(); return (elementType == Path.class) ? path : path.toFile(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java index ad6c7f8f2143..c956ee43a956 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java @@ -14,6 +14,7 @@ import java.util.Optional; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; @@ -89,7 +90,8 @@ public String toString() { // @formatter:on } - private static Object nullSafeGet(Optional optional) { + @SuppressWarnings("OptionalAssignedToNull") + private static @Nullable Object nullSafeGet(@Nullable Optional optional) { return optional != null ? optional.orElse(null) : null; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java index e5be5afefc3c..a0f01842181e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.extension; +import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MICROSECONDS; @@ -56,7 +57,7 @@ TimeoutDuration parse(CharSequence text) throws DateTimeParseException { long value = Long.parseLong(matcher.group(1)); String unitAbbreviation = matcher.group(2); TimeUnit unit = unitAbbreviation == null ? SECONDS - : UNITS_BY_ABBREVIATION.get(unitAbbreviation.toLowerCase(Locale.ENGLISH)); + : requireNonNull(UNITS_BY_ABBREVIATION.get(unitAbbreviation.toLowerCase(Locale.ENGLISH))); return new TimeoutDuration(value, unit); } throw new DateTimeParseException("Timeout duration is not in the expected format ( [ns|μs|ms|s|m|h|d])", diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java index e911ac35ad74..2c52407d0c82 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java @@ -12,6 +12,7 @@ import java.util.concurrent.TimeoutException; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -22,7 +23,8 @@ class TimeoutExceptionFactory { private TimeoutExceptionFactory() { } - static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration, Throwable failure) { + static TimeoutException create(String methodSignature, TimeoutDuration timeoutDuration, + @Nullable Throwable failure) { String message = "%s timed out after %s".formatted( Preconditions.notNull(methodSignature, "method signature must not be null"), Preconditions.notNull(timeoutDuration, "timeout duration must not be null")); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java index 555fb053ad1b..46c6a502e2bc 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.extension; +import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; @@ -18,6 +19,7 @@ import java.util.Optional; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -153,15 +155,15 @@ private T interceptTestableMethod(Invocation invocation, } private T intercept(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext, TimeoutDuration explicitTimeout, TimeoutProvider defaultTimeoutProvider) - throws Throwable { + ExtensionContext extensionContext, @Nullable TimeoutDuration explicitTimeout, + TimeoutProvider defaultTimeoutProvider) throws Throwable { TimeoutDuration timeout = explicitTimeout == null ? getDefaultTimeout(extensionContext, defaultTimeoutProvider) : explicitTimeout; return decorate(invocation, invocationContext, extensionContext, timeout).proceed(); } - private TimeoutDuration getDefaultTimeout(ExtensionContext extensionContext, + private @Nullable TimeoutDuration getDefaultTimeout(ExtensionContext extensionContext, TimeoutProvider defaultTimeoutProvider) { return defaultTimeoutProvider.apply(getGlobalTimeoutConfiguration(extensionContext)).orElse(null); @@ -169,12 +171,12 @@ private TimeoutDuration getDefaultTimeout(ExtensionContext extensionContext, private TimeoutConfiguration getGlobalTimeoutConfiguration(ExtensionContext extensionContext) { ExtensionContext root = extensionContext.getRoot(); - return root.getStore(NAMESPACE).getOrComputeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, - key -> new TimeoutConfiguration(root), TimeoutConfiguration.class); + return requireNonNull(root.getStore(NAMESPACE).getOrComputeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, + key -> new TimeoutConfiguration(root), TimeoutConfiguration.class)); } private Invocation decorate(Invocation invocation, ReflectiveInvocationContext invocationContext, - ExtensionContext extensionContext, TimeoutDuration timeout) { + ExtensionContext extensionContext, @Nullable TimeoutDuration timeout) { if (timeout == null || isTimeoutDisabled(extensionContext)) { return invocation; @@ -194,7 +196,7 @@ private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext) { return annotationThreadMode; } - private ThreadMode getAnnotationThreadMode(ExtensionContext extensionContext) { + private @Nullable ThreadMode getAnnotationThreadMode(ExtensionContext extensionContext) { return extensionContext.getStore(NAMESPACE).get(TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY, ThreadMode.class); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java index ff29b09a9c8f..96ac809f2bb2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/package-info.java @@ -2,4 +2,7 @@ * Test extensions specific to the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine.extension; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java index 17af1fecb552..cb01093df2b9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/package-info.java @@ -2,4 +2,7 @@ * Core package for the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java index 1a9d753d305e..a72d013134f4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java @@ -20,6 +20,7 @@ import java.lang.reflect.Type; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ReflectionSupport; @API(status = INTERNAL, since = "6.0") @@ -37,7 +38,7 @@ public static Type getGenericReturnType(Method method) { : method.getGenericReturnType(); } - public static Object invoke(Method method, Object target, Object[] arguments) { + public static @Nullable Object invoke(Method method, @Nullable Object target, @Nullable Object[] arguments) { if (isKotlinSuspendingFunction(method)) { return invokeKotlinSuspendingFunction(method, target, arguments); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java index 6eecfd0f5120..83ffb2de0923 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/package-info.java @@ -2,4 +2,7 @@ * Internal support classes for the JUnit Jupiter test engine. */ +@NullMarked package org.junit.jupiter.engine.support; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts index b932ffe2021f..810408bfd4c4 100644 --- a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts +++ b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") id("junitbuild.junit4-compatibility") } @@ -11,6 +12,7 @@ dependencies { api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) @@ -23,10 +25,12 @@ tasks { jar { bundle { val importAPIGuardian: String by extra + val importJSpecify: String by extra bnd(""" # Import JUnit4 packages with a version Import-Package: \ $importAPIGuardian,\ + $importJSpecify,\ org.junit;version="[${libs.versions.junit4Min.get()},5)",\ org.junit.platform.commons.logging;status=INTERNAL,\ org.junit.rules;version="[${libs.versions.junit4Min.get()},5)",\ diff --git a/junit-jupiter-migrationsupport/src/main/java/module-info.java b/junit-jupiter-migrationsupport/src/main/java/module-info.java index ced462bec0db..e89e043930e3 100644 --- a/junit-jupiter-migrationsupport/src/main/java/module-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/module-info.java @@ -14,8 +14,11 @@ * @since 5.0 */ module org.junit.jupiter.migrationsupport { - requires transitive junit; // 4 + requires static transitive org.apiguardian.api; + requires static org.jspecify; + + requires transitive junit; // 4 requires transitive org.junit.jupiter.api; requires org.junit.platform.commons; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java index 59ab33098a59..efceb5723117 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java @@ -16,6 +16,7 @@ import java.lang.reflect.AnnotatedElement; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; @@ -53,7 +54,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con .orElse(ENABLED); } - private ConditionEvaluationResult toResult(AnnotatedElement element, Ignore annotation) { + private ConditionEvaluationResult toResult(@Nullable AnnotatedElement element, Ignore annotation) { String value = annotation.value(); String reason = StringUtils.isNotBlank(value) ? value : element + " is disabled via @org.junit.Ignore"; return ConditionEvaluationResult.disabled(reason); diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java index 530ff605d558..27c646ec3448 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/package-info.java @@ -4,4 +4,7 @@ * Jupiter. */ +@NullMarked package org.junit.jupiter.migrationsupport.conditions; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java index 236a13fded50..1f14ec941a3c 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/package-info.java @@ -2,4 +2,7 @@ * Support for migrating from JUnit 4 to JUnit Jupiter. */ +@NullMarked package org.junit.jupiter.migrationsupport; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java index adecfda66993..bf6abc2bcf39 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java @@ -57,8 +57,8 @@ public void handleTestExecutionException(ExtensionContext context, Throwable thr @Override public void afterEach(ExtensionContext context) throws Exception { - boolean handled = getStore(context).getOrComputeIfAbsent(EXCEPTION_WAS_HANDLED, key -> FALSE, Boolean.class); - if (!handled) { + Boolean handled = getStore(context).getOrComputeIfAbsent(EXCEPTION_WAS_HANDLED, key -> FALSE, Boolean.class); + if (handled != null && !handled) { this.support.afterEach(context); } } diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java index f424126d6fdd..a749d1f825a0 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java @@ -11,6 +11,7 @@ package org.junit.jupiter.migrationsupport.rules; import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; import static org.junit.platform.commons.support.AnnotationSupport.findPublicAnnotatedFields; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; @@ -146,8 +147,8 @@ private List getRuleAnnotatedMembers(ExtensionContext c Object testInstance = context.getRequiredTestInstance(); Namespace namespace = Namespace.create(TestRuleSupport.class, context.getRequiredTestClass()); // @formatter:off - return new ArrayList<>(context.getStore(namespace) - .getOrComputeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class)); + return new ArrayList(requireNonNull(context.getStore(namespace) + .getOrComputeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class))); // @formatter:on } diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java index d54bdaf1fc52..dce1c60fba92 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java @@ -17,6 +17,7 @@ import java.lang.reflect.Method; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.migrationsupport.rules.member.TestRuleAnnotatedMember; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ClassUtils; @@ -37,11 +38,11 @@ public AbstractTestRuleAdapter(TestRuleAnnotatedMember annotatedMember, Class adapteeClass + " is not assignable from " + this.target.getClass()); } - protected Object executeMethod(String name) { + protected @Nullable Object executeMethod(String name) { return executeMethod(name, new Class[0]); } - protected Object executeMethod(String methodName, Class[] parameterTypes, Object... arguments) { + protected @Nullable Object executeMethod(String methodName, Class[] parameterTypes, Object... arguments) { Method method = findMethod(this.target.getClass(), methodName, parameterTypes).orElseThrow( () -> new JUnitException("Failed to find method %s(%s) in class %s".formatted(methodName, ClassUtils.nullSafeToString(parameterTypes), this.target.getClass().getName()))); diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java index d69bee9757cc..8353b255297f 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/package-info.java @@ -2,4 +2,7 @@ * Simple wrappers for JUnit 4 rules to overcome visibility limitations of the JUnit 4 implementations. */ +@NullMarked package org.junit.jupiter.migrationsupport.rules.adapter; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java index be6ea055b87d..7e3557a0a085 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java @@ -10,6 +10,8 @@ package org.junit.jupiter.migrationsupport.rules.member; +import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.util.Preconditions; import org.junit.rules.TestRule; /** @@ -19,8 +21,8 @@ abstract class AbstractTestRuleAnnotatedMember implements TestRuleAnnotatedMembe private final TestRule testRule; - AbstractTestRuleAnnotatedMember(TestRule testRule) { - this.testRule = testRule; + AbstractTestRuleAnnotatedMember(@Nullable TestRule testRule) { + this.testRule = Preconditions.notNull(testRule, "TestRule must not be null"); } @Override diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java index 85ce964ae4f6..d60c3221db07 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/package-info.java @@ -2,4 +2,7 @@ * Abstractions for members which can be targets of JUnit 4 rule annotations. */ +@NullMarked package org.junit.jupiter.migrationsupport.rules.member; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java index 752ebb7e1e08..6532d84fcb78 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/package-info.java @@ -2,4 +2,7 @@ * Extensions which provide (limited) support for JUnit 4 rules within JUnit Jupiter. */ +@NullMarked package org.junit.jupiter.migrationsupport.rules; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 6bf26d8c4581..45b9b56127bb 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -1,6 +1,9 @@ import junitbuild.extensions.javaModuleName +import net.ltgt.gradle.errorprone.errorprone +import net.ltgt.gradle.nullaway.nullaway plugins { + id("junitbuild.java-nullability-conventions") id("junitbuild.kotlin-library-conventions") id("junitbuild.shadow-conventions") id("junitbuild.jmh-conventions") @@ -15,6 +18,7 @@ dependencies { api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) shadowed(libs.univocity.parsers) @@ -54,6 +58,14 @@ tasks { "--add-reads", "${javaModuleName}=univocity.parsers" )) } + compileJmhJava { + options.compilerArgs.add("-Xlint:-processing") + options.errorprone.nullaway { + customInitializerAnnotations.add( + "org.openjdk.jmh.annotations.Setup", + ) + } + } javadoc { (options as StandardJavadocDocletOptions).apply { addStringOption("-add-modules", "univocity.parsers") diff --git a/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java b/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java index 592be9f94445..0d57127007e1 100644 --- a/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java +++ b/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterBenchmarks.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params; +import static java.util.Objects.requireNonNull; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DEFAULT_DISPLAY_NAME; import static org.junit.jupiter.params.ParameterizedInvocationConstants.DISPLAY_NAME_PLACEHOLDER; @@ -50,7 +51,9 @@ public void formatTestNames(Blackhole blackhole) throws Exception { var method = TestCase.class.getDeclaredMethod("parameterizedTest", int.class); var formatter = new ParameterizedInvocationNameFormatter( DISPLAY_NAME_PLACEHOLDER + " " + DEFAULT_DISPLAY_NAME + " ({0})", "displayName", - new ParameterizedTestContext(TestCase.class, method, method.getAnnotation(ParameterizedTest.class)), 512); + new ParameterizedTestContext(TestCase.class, method, + requireNonNull(method.getAnnotation(ParameterizedTest.class))), + 512); for (int i = 0; i < argumentsList.size(); i++) { Arguments arguments = argumentsList.get(i); blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments))); diff --git a/junit-jupiter-params/src/main/java/module-info.java b/junit-jupiter-params/src/main/java/module-info.java index 35569b82dd9d..f7ef36172179 100644 --- a/junit-jupiter-params/src/main/java/module-info.java +++ b/junit-jupiter-params/src/main/java/module-info.java @@ -14,7 +14,10 @@ * @since 5.0 */ module org.junit.jupiter.params { + requires static transitive org.apiguardian.api; + requires static org.jspecify; + requires transitive org.junit.jupiter.api; requires transitive org.junit.platform.commons; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/AbstractParameterizedClassInvocationLifecycleMethodInvoker.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/AbstractParameterizedClassInvocationLifecycleMethodInvoker.java index 0d20faee725b..2b836b9aed46 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/AbstractParameterizedClassInvocationLifecycleMethodInvoker.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/AbstractParameterizedClassInvocationLifecycleMethodInvoker.java @@ -10,6 +10,8 @@ package org.junit.jupiter.params; +import static java.util.Objects.requireNonNull; + import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; @@ -52,9 +54,9 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return this.lifecycleMethod.parameterResolver // + return requireNonNull(this.lifecycleMethod.parameterResolver // .resolve(parameterContext, extensionContext, this.arguments, this.invocationIndex, - this.resolutionCache); + this.resolutionCache)); } protected void invoke(ExtensionContext context) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java index 706f33971cb5..4495eec648c0 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java @@ -10,6 +10,8 @@ package org.junit.jupiter.params; +import static java.util.Objects.requireNonNull; + import java.util.Arrays; import java.util.Optional; @@ -72,7 +74,7 @@ private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration( String key = ARGUMENT_COUNT_VALIDATION_KEY; ArgumentCountValidationMode fallback = ArgumentCountValidationMode.NONE; ExtensionContext.Store store = getStore(extensionContext); - return store.getOrComputeIfAbsent(key, __ -> { + return requireNonNull(store.getOrComputeIfAbsent(key, __ -> { Optional optionalConfigValue = extensionContext.getConfigurationParameter(key); if (optionalConfigValue.isPresent()) { String configValue = optionalConfigValue.get(); @@ -96,7 +98,7 @@ private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration( else { return fallback; } - }, ArgumentCountValidationMode.class); + }, ArgumentCountValidationMode.class)); } private static String pluralize(int count, String singular, String plural) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentSetLifecycleMethod.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentSetLifecycleMethod.java index 13ed374ebf14..834cecb4fb2f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentSetLifecycleMethod.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentSetLifecycleMethod.java @@ -12,6 +12,7 @@ import java.lang.reflect.Method; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.platform.commons.JUnitException; @@ -51,6 +52,7 @@ public Object resolve(ParameterContext parameterContext, ExtensionContext extens boolean supports(ParameterContext parameterContext); + @Nullable Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/EvaluatedArgumentSet.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/EvaluatedArgumentSet.java index 99c609d8969c..de8573073b55 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/EvaluatedArgumentSet.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/EvaluatedArgumentSet.java @@ -15,6 +15,7 @@ import java.util.function.Function; import java.util.function.IntUnaryOperator; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Named; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments.ArgumentSet; @@ -32,25 +33,29 @@ class EvaluatedArgumentSet { static EvaluatedArgumentSet allOf(Arguments arguments) { + @Nullable Object[] all = arguments.get(); return create(all, all, arguments); } static EvaluatedArgumentSet of(Arguments arguments, IntUnaryOperator consumedLengthComputer) { + @Nullable Object[] all = arguments.get(); + @Nullable Object[] consumed = dropSurplus(all, consumedLengthComputer.applyAsInt(all.length)); return create(all, consumed, arguments); } - private static EvaluatedArgumentSet create(Object[] all, Object[] consumed, Arguments arguments) { + private static EvaluatedArgumentSet create(@Nullable Object[] all, @Nullable Object[] consumed, + Arguments arguments) { return new EvaluatedArgumentSet(all, consumed, determineName(arguments)); } - private final Object[] all; - private final Object[] consumed; + private final @Nullable Object[] all; + private final @Nullable Object[] consumed; private final Optional name; - private EvaluatedArgumentSet(Object[] all, Object[] consumed, Optional name) { + private EvaluatedArgumentSet(@Nullable Object[] all, @Nullable Object[] consumed, Optional name) { this.all = all; this.consumed = consumed; this.name = name; @@ -60,6 +65,7 @@ int getTotalLength() { return this.all.length; } + @Nullable Object[] getAllPayloads() { return extractFromNamed(this.all, Named::getPayload); } @@ -68,14 +74,17 @@ int getConsumedLength() { return this.consumed.length; } + @Nullable Object[] getConsumedNames() { return extractFromNamed(this.consumed, Named::getName); } + @Nullable Object[] getConsumedPayloads() { return extractFromNamed(this.consumed, Named::getPayload); } + @Nullable Object getConsumedPayload(int index) { return extractFromNamed(this.consumed[index], Named::getPayload); } @@ -84,7 +93,7 @@ Optional getName() { return this.name; } - private static Object[] dropSurplus(Object[] arguments, int newLength) { + private static @Nullable Object[] dropSurplus(@Nullable Object[] arguments, int newLength) { Preconditions.condition(newLength <= arguments.length, () -> "New length %d must be less than or equal to the total length %d".formatted(newLength, arguments.length)); @@ -98,13 +107,15 @@ private static Optional determineName(Arguments arguments) { return Optional.empty(); } - private static Object[] extractFromNamed(Object[] arguments, Function, Object> mapper) { + private static @Nullable Object[] extractFromNamed(@Nullable Object[] arguments, + Function, @Nullable Object> mapper) { return Arrays.stream(arguments) // .map(argument -> extractFromNamed(argument, mapper)) // .toArray(); } - private static Object extractFromNamed(Object argument, Function, Object> mapper) { + private static @Nullable Object extractFromNamed(@Nullable Object argument, + Function, @Nullable Object> mapper) { return argument instanceof Named named ? mapper.apply(named) : argument; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java index 2f45b6d7f606..3f134156eba6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassExtension.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params; +import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.params.ParameterizedClassContext.InjectionType.CONSTRUCTOR; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; @@ -124,8 +125,8 @@ private static ParameterizedClassContext createClassContext(ExtensionContext ext } private ParameterizedClassContext getDeclarationContext(ExtensionContext extensionContext) { - return getStore(extensionContext)// - .get(DECLARATION_CONTEXT_KEY, ParameterizedClassContext.class); + return requireNonNull(getStore(extensionContext)// + .get(DECLARATION_CONTEXT_KEY, ParameterizedClassContext.class)); } private Store getStore(ExtensionContext context) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContext.java index e310abb94843..62e198e0cf2a 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationContext.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; @@ -71,6 +72,7 @@ private void validateArgumentCount(ExtensionContext context) { private void storeParameterInfo(ExtensionContext context) { ParameterDeclarations declarations = this.declarationContext.getResolverFacade().getIndexedParameterDeclarations(); ClassLoader classLoader = getClassLoader(this.declarationContext.getTestClass()); + @Nullable Object[] arguments = this.arguments.getConsumedPayloads(); ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(context, invocationIndex, classLoader, arguments); new DefaultParameterInfo(declarations, accessor).store(context); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatter.java index ebaf50c536df..22eb77dc327f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatter.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_WITH_NAMES_PLACEHOLDER; @@ -34,6 +35,7 @@ import java.util.function.Function; import java.util.stream.IntStream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; @@ -130,7 +132,7 @@ private PartialFormatter[] parse(String pattern, String displayName, return result.toArray(new PartialFormatter[0]); } - private static PlaceholderPosition findFirstPlaceholder(PartialFormatters formatters, String segment) { + private static @Nullable PlaceholderPosition findFirstPlaceholder(PartialFormatters formatters, String segment) { if (segment.length() < formatters.minimumPlaceholderLength) { return null; } @@ -210,10 +212,10 @@ private static class PlaceholderPosition { private static class ArgumentsContext { private final int invocationIndex; - private final Object[] consumedArguments; + private final @Nullable Object[] consumedArguments; private final Optional argumentSetName; - ArgumentsContext(int invocationIndex, Object[] consumedArguments, Optional argumentSetName) { + ArgumentsContext(int invocationIndex, @Nullable Object[] consumedArguments, Optional argumentSetName) { this.invocationIndex = invocationIndex; this.consumedArguments = consumedArguments; this.argumentSetName = argumentSetName; @@ -268,8 +270,9 @@ public synchronized void append(ArgumentsContext context, StringBuffer result) { this.messageFormat.format(makeReadable(context.consumedArguments), result, new FieldPosition(0)); } - private Object[] makeReadable(Object[] arguments) { + private @Nullable Object[] makeReadable(@Nullable Object[] arguments) { Format[] formats = messageFormat.getFormatsByArgumentIndex(); + @Nullable Object[] result = Arrays.copyOf(arguments, Math.min(arguments.length, formats.length), Object[].class); for (int i = 0; i < result.length; i++) { if (formats[i] == null) { @@ -279,7 +282,7 @@ private Object[] makeReadable(Object[] arguments) { return result; } - private String truncateIfExceedsMaxLength(String argument) { + private @Nullable String truncateIfExceedsMaxLength(@Nullable String argument) { if (argument != null && argument.length() > this.argumentMaxLength) { return argument.substring(0, this.argumentMaxLength - 1) + ELLIPSIS; } @@ -316,7 +319,7 @@ void put(String placeholder, PartialFormatter formatter) { } PartialFormatter get(String placeholder) { - return formattersByPlaceholder.get(placeholder); + return requireNonNull(formattersByPlaceholder.get(placeholder)); } Set placeholders() { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationParameterResolver.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationParameterResolver.java index 84faf1971d23..193aff285e76 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationParameterResolver.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationParameterResolver.java @@ -12,6 +12,7 @@ import java.lang.reflect.Executable; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; @@ -50,7 +51,7 @@ public final boolean supportsParameter(ParameterContext parameterContext, Extens } @Override - public final Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + public final @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return this.resolverFacade.resolve(parameterContext, extensionContext, this.arguments, this.invocationIndex, diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java index 1b4393d4f3a3..31de4ad836aa 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params; +import static java.util.Objects.requireNonNull; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.util.Optional; @@ -56,8 +57,8 @@ public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext exte } private ParameterizedTestContext getDeclarationContext(ExtensionContext extensionContext) { - return getStore(extensionContext)// - .get(DECLARATION_CONTEXT_KEY, ParameterizedTestContext.class); + return requireNonNull(getStore(extensionContext)// + .get(DECLARATION_CONTEXT_KEY, ParameterizedTestContext.class)); } private ExtensionContext.Store getStore(ExtensionContext context) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolutionCache.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolutionCache.java index eeec256dccd0..b692cb5a0e5d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolutionCache.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolutionCache.java @@ -14,6 +14,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.params.support.ParameterDeclaration; /** @@ -27,7 +28,8 @@ static ResolutionCache enabled() { ResolutionCache DISABLED = (__, resolver) -> resolver.get(); - Object resolve(ParameterDeclaration declaration, Supplier resolver); + @Nullable + Object resolve(ParameterDeclaration declaration, Supplier<@Nullable Object> resolver); class Concurrent implements ResolutionCache { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java index 54dc04b767f4..aa81b1d92c34 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java @@ -12,6 +12,7 @@ import static java.lang.System.lineSeparator; import static java.util.Collections.unmodifiableList; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; @@ -41,6 +42,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; @@ -251,6 +253,7 @@ ArgumentSetLifecycleMethod.ParameterResolver createLifecycleMethodParameterResol * Resolve the parameter for the supplied context using the supplied * arguments. */ + @Nullable Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { @@ -304,8 +307,9 @@ private void setField(Object testInstance, FieldParameterDeclaration declaration } } - private Object resolve(ResolvableParameterDeclaration parameterDeclaration, ExtensionContext extensionContext, - EvaluatedArgumentSet arguments, int invocationIndex, Optional parameterContext) { + private @Nullable Object resolve(ResolvableParameterDeclaration parameterDeclaration, + ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, + Optional parameterContext) { Resolver resolver = getResolver(extensionContext, parameterDeclaration); return parameterDeclaration.resolve(resolver, extensionContext, arguments, invocationIndex, parameterContext); } @@ -465,9 +469,11 @@ private static ParameterResolutionException parameterResolutionException(String private interface Resolver { + @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex); + @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex); @@ -486,8 +492,8 @@ private static Converter createDefault(ExtensionContext context) { } @Override - public Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext, - EvaluatedArgumentSet arguments, int invocationIndex) { + public @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, + ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { Object argument = arguments.getConsumedPayload(parameterIndex); try { return this.argumentConverter.convert(argument, parameterContext); @@ -498,7 +504,7 @@ public Object resolve(ParameterContext parameterContext, int parameterIndex, Ext } @Override - public Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, + public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { Object argument = arguments.getConsumedPayload(fieldContext.getParameterIndex()); try { @@ -527,9 +533,9 @@ protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetT } @Override - public Object resolve(ParameterContext parameterContext, int parameterIndex, ExtensionContext extensionContext, - EvaluatedArgumentSet arguments, int invocationIndex) { - ArgumentsAccessor accessor = ParameterInfo.get(extensionContext).getArguments(); + public @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, + ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { + ArgumentsAccessor accessor = requireNonNull(ParameterInfo.get(extensionContext)).getArguments(); try { return this.argumentsAggregator.aggregateArguments(accessor, parameterContext); } @@ -540,9 +546,9 @@ public Object resolve(ParameterContext parameterContext, int parameterIndex, Ext } @Override - public Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, + public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex) { - ArgumentsAccessor accessor = ParameterInfo.get(extensionContext).getArguments(); + ArgumentsAccessor accessor = requireNonNull(ParameterInfo.get(extensionContext)).getArguments(); try { return this.argumentsAggregator.aggregateArguments(accessor, fieldContext); } @@ -618,7 +624,7 @@ boolean isAggregator() { || isAnnotated(getAnnotatedElement(), AggregateWith.class); } - protected abstract Object resolve(Resolver resolver, ExtensionContext extensionContext, + protected abstract @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, Optional originalParameterContext); } @@ -659,8 +665,9 @@ public Optional getParameterName() { } @Override - public Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, - int invocationIndex, Optional originalParameterContext) { + public @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, + EvaluatedArgumentSet arguments, int invocationIndex, + Optional originalParameterContext) { return resolver.resolve(this, extensionContext, arguments, invocationIndex); } } @@ -698,8 +705,9 @@ public Optional getParameterName() { } @Override - public Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, - int invocationIndex, Optional originalParameterContext) { + public @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, + EvaluatedArgumentSet arguments, int invocationIndex, + Optional originalParameterContext) { ParameterContext parameterContext = originalParameterContext // .filter(it -> it.getParameter().equals(this.parameter)) // .orElseGet(() -> toParameterContext(extensionContext, originalParameterContext)); @@ -759,7 +767,7 @@ public boolean supports(ParameterContext parameterContext) { } @Override - public Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, + public @Nullable Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, ResolutionCache resolutionCache) { ResolvableParameterDeclaration actualDeclaration = this.lifecycleMethodResolverFacade // diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java index 5e31a35d288c..536c19db6a19 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java @@ -15,6 +15,7 @@ import java.util.List; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * {@code ArgumentsAccessor} defines the public API for accessing arguments provided @@ -52,6 +53,7 @@ public interface ArgumentsAccessor { * equal to zero and less than {@link #size} * @return the value at the given index, potentially {@code null} */ + @Nullable Object get(int index) throws ArgumentAccessException; /** @@ -63,7 +65,7 @@ public interface ArgumentsAccessor { * @param requiredType the required type of the value; never {@code null} * @return the value at the given index, potentially {@code null} */ - T get(int index, Class requiredType) throws ArgumentAccessException; + @Nullable T get(int index, Class requiredType) throws ArgumentAccessException; /** * Get the value of the argument at the given index as a {@link Character}, @@ -75,6 +77,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Character getCharacter(int index) throws ArgumentAccessException; /** @@ -87,6 +90,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Boolean getBoolean(int index) throws ArgumentAccessException; /** @@ -99,6 +103,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Byte getByte(int index) throws ArgumentAccessException; /** @@ -111,6 +116,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Short getShort(int index) throws ArgumentAccessException; /** @@ -123,6 +129,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Integer getInteger(int index) throws ArgumentAccessException; /** @@ -135,6 +142,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Long getLong(int index) throws ArgumentAccessException; /** @@ -147,6 +155,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Float getFloat(int index) throws ArgumentAccessException; /** @@ -159,6 +168,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable Double getDouble(int index) throws ArgumentAccessException; /** @@ -171,6 +181,7 @@ public interface ArgumentsAccessor { * @throws ArgumentAccessException if the value cannot be accessed * or converted to the desired type */ + @Nullable String getString(int index) throws ArgumentAccessException; /** @@ -181,12 +192,13 @@ public interface ArgumentsAccessor { /** * Get all arguments in this accessor as an array. */ + @Nullable Object[] toArray(); /** * Get all arguments in this accessor as an immutable list. */ - List toList(); + List<@Nullable Object> toList(); /** * Get the index of the current test invocation. diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java index 905b69e8fe33..8a6ada359a1a 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.params.support.FieldContext; @@ -65,6 +66,7 @@ public interface ArgumentsAggregator { * @throws ArgumentsAggregationException if an error occurs during the * aggregation */ + @Nullable Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException; @@ -83,7 +85,7 @@ Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) * @since 5.13 */ @API(status = EXPERIMENTAL, since = "5.13") - default Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) + default @Nullable Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) throws ArgumentsAggregationException { throw new JUnitException( String.format("ArgumentsAggregator does not override the convert(ArgumentsAccessor, FieldContext) method. " diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java index 62750bbae9e8..f33ac3dbdf0e 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java @@ -18,6 +18,7 @@ import java.util.function.BiFunction; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.converter.DefaultArgumentConverter; import org.junit.platform.commons.util.ClassUtils; @@ -37,20 +38,21 @@ public class DefaultArgumentsAccessor implements ArgumentsAccessor { private final int invocationIndex; - private final Object[] arguments; - private final BiFunction, Object> converter; + private final @Nullable Object[] arguments; + private final BiFunction<@Nullable Object, Class, @Nullable Object> converter; public static DefaultArgumentsAccessor create(ExtensionContext context, int invocationIndex, - ClassLoader classLoader, Object[] arguments) { + ClassLoader classLoader, @Nullable Object[] arguments) { Preconditions.notNull(classLoader, "ClassLoader must not be null"); - BiFunction, Object> converter = (source, targetType) -> new DefaultArgumentConverter(context) // - .convert(source, targetType, classLoader); + BiFunction<@Nullable Object, Class, @Nullable Object> converter = (source, + targetType) -> new DefaultArgumentConverter(context) // + .convert(source, targetType, classLoader); return new DefaultArgumentsAccessor(converter, invocationIndex, arguments); } - private DefaultArgumentsAccessor(BiFunction, Object> converter, int invocationIndex, - Object... arguments) { + private DefaultArgumentsAccessor(BiFunction<@Nullable Object, Class, @Nullable Object> converter, + int invocationIndex, @Nullable Object... arguments) { Preconditions.condition(invocationIndex >= 1, () -> "Invocation index must be >= 1"); this.converter = Preconditions.notNull(converter, "Converter must not be null"); this.invocationIndex = invocationIndex; @@ -58,6 +60,7 @@ private DefaultArgumentsAccessor(BiFunction, Object> converter, } @Override + @Nullable public Object get(int index) { Preconditions.condition(index >= 0 && index < this.arguments.length, () -> "index must be >= 0 and < %d".formatted(this.arguments.length)); @@ -65,7 +68,7 @@ public Object get(int index) { } @Override - public T get(int index, Class requiredType) { + public @Nullable T get(int index, Class requiredType) { Preconditions.notNull(requiredType, "requiredType must not be null"); Object value = get(index); try { @@ -81,47 +84,47 @@ public T get(int index, Class requiredType) { } @Override - public Character getCharacter(int index) { + public @Nullable Character getCharacter(int index) { return get(index, Character.class); } @Override - public Boolean getBoolean(int index) { + public @Nullable Boolean getBoolean(int index) { return get(index, Boolean.class); } @Override - public Byte getByte(int index) { + public @Nullable Byte getByte(int index) { return get(index, Byte.class); } @Override - public Short getShort(int index) { + public @Nullable Short getShort(int index) { return get(index, Short.class); } @Override - public Integer getInteger(int index) { + public @Nullable Integer getInteger(int index) { return get(index, Integer.class); } @Override - public Long getLong(int index) { + public @Nullable Long getLong(int index) { return get(index, Long.class); } @Override - public Float getFloat(int index) { + public @Nullable Float getFloat(int index) { return get(index, Float.class); } @Override - public Double getDouble(int index) { + public @Nullable Double getDouble(int index) { return get(index, Double.class); } @Override - public String getString(int index) { + public @Nullable String getString(int index) { return get(index, String.class); } @@ -131,12 +134,12 @@ public int size() { } @Override - public Object[] toArray() { + public @Nullable Object[] toArray() { return Arrays.copyOf(this.arguments, this.arguments.length); } @Override - public List toList() { + public List<@Nullable Object> toList() { return Collections.unmodifiableList(Arrays.asList(this.arguments)); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/SimpleArgumentsAggregator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/SimpleArgumentsAggregator.java index 7537e5a64e46..f46b842535ec 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/SimpleArgumentsAggregator.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/SimpleArgumentsAggregator.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AnnotatedElementContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; @@ -32,17 +33,17 @@ public SimpleArgumentsAggregator() { } @Override - public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) + public @Nullable Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException { return aggregateArguments(accessor, context.getParameter().getType(), context, context.getIndex()); } @Override - public Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) + public @Nullable Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) throws ArgumentsAggregationException { return aggregateArguments(accessor, context.getField().getType(), context, context.getParameterIndex()); } - protected abstract Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, + protected abstract @Nullable Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java index e7129488d84f..a56f2a1280a1 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/package-info.java @@ -4,4 +4,7 @@ * {@link org.junit.jupiter.params.aggregator.AggregateWith} annotation. */ +@NullMarked package org.junit.jupiter.params.aggregator; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java index 40dc578f40b4..b36a604e86e7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java @@ -10,11 +10,13 @@ package org.junit.jupiter.params.converter; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Annotation; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.AnnotationConsumer; import org.junit.jupiter.params.support.FieldContext; @@ -34,25 +36,28 @@ public abstract class AnnotationBasedArgumentConverter implements ArgumentConverter, AnnotationConsumer { + @Nullable + private A annotation; + public AnnotationBasedArgumentConverter() { } - private A annotation; - @Override public final void accept(A annotation) { - Preconditions.notNull(annotation, "annotation must not be null"); - this.annotation = annotation; + this.annotation = Preconditions.notNull(annotation, "annotation must not be null"); + ; } @Override - public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { - return convert(source, context.getParameter().getType(), this.annotation); + public final @Nullable Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { + return convert(source, context.getParameter().getType(), requireNonNull(this.annotation)); } @Override - public final Object convert(Object source, FieldContext context) throws ArgumentConversionException { - return convert(source, context.getField().getType(), this.annotation); + public final @Nullable Object convert(@Nullable Object source, FieldContext context) + throws ArgumentConversionException { + return convert(source, context.getField().getType(), requireNonNull(this.annotation)); } /** @@ -68,7 +73,7 @@ public final Object convert(Object source, FieldContext context) throws Argument * @throws ArgumentConversionException in case an error occurs during the * conversion */ - protected abstract Object convert(Object source, Class targetType, A annotation) + protected abstract @Nullable Object convert(@Nullable Object source, Class targetType, A annotation) throws ArgumentConversionException; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java index 08a6228bd127..64d5b304aceb 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java @@ -15,6 +15,7 @@ import java.io.Serial; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** @@ -31,11 +32,11 @@ public class ArgumentConversionException extends JUnitException { @Serial private static final long serialVersionUID = 1L; - public ArgumentConversionException(String message) { + public ArgumentConversionException(@Nullable String message) { super(message); } - public ArgumentConversionException(String message, Throwable cause) { + public ArgumentConversionException(@Nullable String message, @Nullable Throwable cause) { super(message, cause); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java index eae935d66e75..86e9c17b656b 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.params.support.FieldContext; @@ -64,7 +65,8 @@ public interface ArgumentConverter { * @throws ArgumentConversionException if an error occurs during the * conversion */ - Object convert(Object source, ParameterContext context) throws ArgumentConversionException; + @Nullable + Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException; /** * Convert the supplied {@code source} object according to the supplied @@ -80,7 +82,7 @@ public interface ArgumentConverter { * @since 5.13 */ @API(status = EXPERIMENTAL, since = "5.13") - default Object convert(Object source, FieldContext context) throws ArgumentConversionException { + default @Nullable Object convert(@Nullable Object source, FieldContext context) throws ArgumentConversionException { throw new JUnitException( String.format("ArgumentConverter does not override the convert(Object, FieldContext) method. " + "Please report this issue to the maintainers of %s.", diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java index 81e0bf1bc95d..46c29d6a9cc0 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java @@ -24,6 +24,7 @@ import java.util.function.Function; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; @@ -80,20 +81,22 @@ public DefaultArgumentConverter(ExtensionContext context) { } @Override - public final Object convert(Object source, ParameterContext context) { + public final @Nullable Object convert(@Nullable Object source, ParameterContext context) { Class targetType = context.getParameter().getType(); ClassLoader classLoader = getClassLoader(context.getDeclaringExecutable().getDeclaringClass()); return convert(source, targetType, classLoader); } @Override - public final Object convert(Object source, FieldContext context) throws ArgumentConversionException { + public final @Nullable Object convert(@Nullable Object source, FieldContext context) + throws ArgumentConversionException { + Class targetType = context.getField().getType(); ClassLoader classLoader = getClassLoader(context.getField().getDeclaringClass()); return convert(source, targetType, classLoader); } - public final Object convert(Object source, Class targetType, ClassLoader classLoader) { + public final @Nullable Object convert(@Nullable Object source, Class targetType, ClassLoader classLoader) { if (source == null) { if (targetType.isPrimitive()) { throw new ArgumentConversionException( @@ -128,7 +131,8 @@ private LocaleConversionFormat getLocaleConversionFormat() { .orElse(LocaleConversionFormat.BCP_47); } - Object convert(String source, Class targetType, ClassLoader classLoader) { + @Nullable + Object convert(@Nullable String source, Class targetType, ClassLoader classLoader) { return ConversionSupport.convert(source, targetType, classLoader); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java index 6684b472ff18..f91d22d0cf65 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java @@ -27,6 +27,8 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; + /** * @since 5.0 */ @@ -50,7 +52,9 @@ class JavaTimeArgumentConverter extends AnnotationBasedArgumentConverter targetClass, JavaTimeConversionPattern annotation) { + protected @Nullable Object convert(@Nullable Object input, Class targetClass, + JavaTimeConversionPattern annotation) { + if (input == null) { if (annotation.nullable()) { return null; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java index 2cb0f3f922a6..8ef7a6235104 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; @@ -33,12 +34,14 @@ public SimpleArgumentConverter() { } @Override - public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public final @Nullable Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { return convert(source, context.getParameter().getType()); } @Override - public final Object convert(Object source, FieldContext context) throws ArgumentConversionException { + public final @Nullable Object convert(@Nullable Object source, FieldContext context) + throws ArgumentConversionException { return convert(source, context.getField().getType()); } @@ -54,6 +57,7 @@ public final Object convert(Object source, FieldContext context) throws Argument * @throws ArgumentConversionException in case an error occurs during the * conversion */ - protected abstract Object convert(Object source, Class targetType) throws ArgumentConversionException; + protected abstract @Nullable Object convert(@Nullable Object source, Class targetType) + throws ArgumentConversionException; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java index 79b7a42d1549..b2dda27b4639 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.support.FieldContext; import org.junit.platform.commons.util.Preconditions; @@ -30,7 +31,7 @@ * @see SimpleArgumentConverter */ @API(status = STABLE, since = "5.10") -public abstract class TypedArgumentConverter implements ArgumentConverter { +public abstract class TypedArgumentConverter implements ArgumentConverter { private final Class sourceType; private final Class targetType; @@ -48,16 +49,20 @@ protected TypedArgumentConverter(Class sourceType, Class targetType) { } @Override - public final Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public final @Nullable Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { + return convert(source, context.getParameter().getType()); } @Override - public final Object convert(Object source, FieldContext context) throws ArgumentConversionException { + public final @Nullable Object convert(@Nullable Object source, FieldContext context) + throws ArgumentConversionException { + return convert(source, context.getField().getType()); } - private T convert(Object source, Class actualTargetType) { + private T convert(@Nullable Object source, Class actualTargetType) { if (source == null) { return convert(null); } @@ -84,6 +89,6 @@ private T convert(Object source, Class actualTargetType) { * @throws ArgumentConversionException if an error occurs during the * conversion */ - protected abstract T convert(S source) throws ArgumentConversionException; + protected abstract T convert(@Nullable S source) throws ArgumentConversionException; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java index ff813c1864f7..6459e382e89f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/package-info.java @@ -4,4 +4,7 @@ * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. */ +@NullMarked package org.junit.jupiter.params.converter; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java index aa0820df67a8..1261a4a90940 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/package-info.java @@ -2,4 +2,7 @@ * JUnit Jupiter extension for parameterized tests. */ +@NullMarked package org.junit.jupiter.params; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java index bb525eb94648..2deb78bd8889 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java @@ -14,6 +14,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -60,8 +61,9 @@ public interface Arguments { * @apiNote If you need a type-safe way to access some or all of the arguments, * please read the {@linkplain Arguments class-level API note}. * - * @return the arguments; never {@code null} + * @return the arguments; never {@code null} but may contain {@code null} */ + @Nullable Object[] get(); /** @@ -69,12 +71,12 @@ public interface Arguments { * the supplied {@code arguments}. * * @param arguments the arguments to be used for an invocation of the test - * method; must not be {@code null} + * method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} * @see #arguments(Object...) * @see #argumentSet(String, Object...) */ - static Arguments of(Object... arguments) { + static Arguments of(@Nullable Object... arguments) { Preconditions.notNull(arguments, "arguments array must not be null"); return () -> arguments; } @@ -88,12 +90,12 @@ static Arguments of(Object... arguments) { * {@code import static org.junit.jupiter.params.provider.Arguments.arguments;} * * @param arguments the arguments to be used for an invocation of the test - * method; must not be {@code null} + * method; must not be {@code null} but may contain {@code null} * @return an instance of {@code Arguments}; never {@code null} * @since 5.3 * @see #argumentSet(String, Object...) */ - static Arguments arguments(Object... arguments) { + static Arguments arguments(@Nullable Object... arguments) { return of(arguments); } @@ -111,7 +113,7 @@ static Arguments arguments(Object... arguments) { * * @param name the name of the argument set; must not be {@code null} or blank * @param arguments the arguments to be used for an invocation of the test - * method; must not be {@code null} + * method; must not be {@code null} but may contain {@code null} * @return an {@code ArgumentSet}; never {@code null} * @since 5.11 * @see ArgumentSet @@ -119,7 +121,7 @@ static Arguments arguments(Object... arguments) { * @see org.junit.jupiter.params.ParameterizedTest#ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER */ @API(status = EXPERIMENTAL, since = "5.11") - static ArgumentSet argumentSet(String name, Object... arguments) { + static ArgumentSet argumentSet(String name, @Nullable Object... arguments) { return new ArgumentSet(name, arguments); } @@ -137,9 +139,9 @@ final class ArgumentSet implements Arguments { private final String name; - private final Object[] arguments; + private final @Nullable Object[] arguments; - private ArgumentSet(String name, Object[] arguments) { + private ArgumentSet(String name, @Nullable Object[] arguments) { Preconditions.notBlank(name, "name must not be null or blank"); Preconditions.notNull(arguments, "arguments array must not be null"); this.name = name; @@ -155,7 +157,7 @@ public String getName() { } @Override - public Object[] get() { + public @Nullable Object[] get() { return this.arguments; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index 86329b967638..3f961f83c718 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params.provider; +import static java.util.Objects.requireNonNull; import static org.junit.jupiter.params.provider.CsvParserFactory.createParserFor; import static org.junit.platform.commons.util.CollectionUtils.toSet; @@ -24,6 +25,7 @@ import com.univocity.parsers.csv.CsvParser; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; @@ -38,36 +40,34 @@ class CsvArgumentsProvider extends AnnotationBasedArgumentsProvider { private static final String LINE_SEPARATOR = "\n"; - private Set nullValues; - private CsvParser csvParser; - @Override protected Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvSource csvSource) { - this.nullValues = toSet(csvSource.nullValues()); - this.csvParser = createParserFor(csvSource); + Set nullValues = toSet(csvSource.nullValues()); + CsvParser csvParser = createParserFor(csvSource); final boolean textBlockDeclared = !csvSource.textBlock().isEmpty(); Preconditions.condition(csvSource.value().length > 0 ^ textBlockDeclared, () -> "@CsvSource must be declared with either `value` or `textBlock` but not both"); - return textBlockDeclared ? parseTextBlock(csvSource) : parseValueArray(csvSource); + return textBlockDeclared ? parseTextBlock(csvSource, csvParser, nullValues) + : parseValueArray(csvSource, csvParser, nullValues); } - private Stream parseTextBlock(CsvSource csvSource) { + private Stream parseTextBlock(CsvSource csvSource, CsvParser csvParser, Set nullValues) { String textBlock = csvSource.textBlock(); boolean useHeadersInDisplayName = csvSource.useHeadersInDisplayName(); List argumentsList = new ArrayList<>(); try { - List csvRecords = this.csvParser.parseAll(new StringReader(textBlock)); - String[] headers = useHeadersInDisplayName ? getHeaders(this.csvParser) : null; + List csvRecords = csvParser.parseAll(new StringReader(textBlock)); + String[] headers = useHeadersInDisplayName ? getHeaders(csvParser) : null; AtomicInteger index = new AtomicInteger(0); for (String[] csvRecord : csvRecords) { index.incrementAndGet(); Preconditions.notNull(csvRecord, () -> "Record at index " + index + " contains invalid CSV: \"\"\"\n" + textBlock + "\n\"\"\""); - argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); + argumentsList.add(processCsvRecord(csvRecord, nullValues, useHeadersInDisplayName, headers)); } } catch (Throwable throwable) { @@ -77,7 +77,7 @@ private Stream parseTextBlock(CsvSource csvSource) { return argumentsList.stream(); } - private Stream parseValueArray(CsvSource csvSource) { + private Stream parseValueArray(CsvSource csvSource, CsvParser csvParser, Set nullValues) { boolean useHeadersInDisplayName = csvSource.useHeadersInDisplayName(); List argumentsList = new ArrayList<>(); @@ -86,15 +86,15 @@ private Stream parseValueArray(CsvSource csvSource) { AtomicInteger index = new AtomicInteger(0); for (String input : csvSource.value()) { index.incrementAndGet(); - String[] csvRecord = this.csvParser.parseLine(input + LINE_SEPARATOR); + String[] csvRecord = csvParser.parseLine(input + LINE_SEPARATOR); // Lazily retrieve headers if necessary. if (useHeadersInDisplayName && headers == null) { - headers = getHeaders(this.csvParser); + headers = getHeaders(csvParser); continue; } Preconditions.notNull(csvRecord, () -> "Record at index " + index + " contains invalid CSV: \"" + input + "\""); - argumentsList.add(processCsvRecord(csvRecord, this.nullValues, useHeadersInDisplayName, headers)); + argumentsList.add(processCsvRecord(csvRecord, nullValues, useHeadersInDisplayName, headers)); } } catch (Throwable throwable) { @@ -116,18 +116,19 @@ static String[] getHeaders(CsvParser csvParser) { * {@link Named} if necessary (for CSV header support), and returns the * CSV record wrapped in an {@link Arguments} instance. */ - static Arguments processCsvRecord(Object[] csvRecord, Set nullValues, boolean useHeadersInDisplayName, - String[] headers) { + static Arguments processCsvRecord(String[] csvRecord, Set nullValues, boolean useHeadersInDisplayName, + String @Nullable [] headers) { // Nothing to process? if (nullValues.isEmpty() && !useHeadersInDisplayName) { - return Arguments.of(csvRecord); + return Arguments.of((Object[]) csvRecord); } - Preconditions.condition(!useHeadersInDisplayName || (csvRecord.length <= headers.length), + Preconditions.condition(!useHeadersInDisplayName || (csvRecord.length <= requireNonNull(headers).length), () -> "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s".formatted( - csvRecord.length, headers.length, Arrays.toString(csvRecord))); + csvRecord.length, requireNonNull(headers).length, Arrays.toString(csvRecord))); + @Nullable Object[] arguments = new Object[csvRecord.length]; for (int i = 0; i < csvRecord.length; i++) { Object column = csvRecord[i]; @@ -135,13 +136,18 @@ static Arguments processCsvRecord(Object[] csvRecord, Set nullValues, bo column = null; } if (useHeadersInDisplayName) { - column = Named.of(headers[i] + " = " + column, column); + column = asNamed(requireNonNull(headers)[i] + " = " + column, column); } arguments[i] = column; } return Arguments.of(arguments); } + @SuppressWarnings("NullAway") + private static Named<@Nullable Object> asNamed(String name, @Nullable Object column) { + return Named.of(name, column); + } + /** * @return this method always throws an exception and therefore never * returns anything; the return type is merely present to allow this diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index 09635ced0b26..a56ec8270b3a 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params.provider; +import static java.util.Objects.requireNonNull; import static java.util.Spliterators.spliteratorUnknownSize; import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; @@ -33,6 +34,7 @@ import com.univocity.parsers.csv.CsvParser; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; @@ -46,10 +48,6 @@ class CsvFileArgumentsProvider extends AnnotationBasedArgumentsProvider provideArguments(ParameterDeclarations parameters, ExtensionContext context, CsvFileSource csvFileSource) { - this.charset = getCharsetFrom(csvFileSource); - this.numLinesToSkip = csvFileSource.numLinesToSkip(); - this.csvParser = createParserFor(csvFileSource); + Charset charset = getCharsetFrom(csvFileSource); + CsvParser csvParser = createParserFor(csvFileSource); Stream resources = Arrays.stream(csvFileSource.resources()).map(inputStreamProvider::classpathResource); Stream files = Arrays.stream(csvFileSource.files()).map(inputStreamProvider::file); @@ -73,7 +70,7 @@ protected Stream provideArguments(ParameterDeclarations par return Preconditions.notEmpty(sources, "Resources or files must not be empty") .stream() .map(source -> source.open(context)) - .map(inputStream -> beginParsing(inputStream, csvFileSource)) + .map(inputStream -> beginParsing(inputStream, csvFileSource, csvParser, charset)) .flatMap(parser -> toStream(parser, csvFileSource)); // @formatter:on } @@ -87,20 +84,21 @@ private Charset getCharsetFrom(CsvFileSource csvFileSource) { } } - private CsvParser beginParsing(InputStream inputStream, CsvFileSource csvFileSource) { + private CsvParser beginParsing(InputStream inputStream, CsvFileSource csvFileSource, CsvParser csvParser, + Charset charset) { try { - this.csvParser.beginParsing(inputStream, this.charset); + csvParser.beginParsing(inputStream, charset); } catch (Throwable throwable) { throw handleCsvException(throwable, csvFileSource); } - return this.csvParser; + return csvParser; } private Stream toStream(CsvParser csvParser, CsvFileSource csvFileSource) { CsvParserIterator iterator = new CsvParserIterator(csvParser, csvFileSource); return stream(spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) // - .skip(this.numLinesToSkip) // + .skip(csvFileSource.numLinesToSkip()) // .onClose(() -> { try { csvParser.stopParsing(); @@ -117,8 +115,11 @@ private static class CsvParserIterator implements Iterator { private final CsvFileSource csvFileSource; private final boolean useHeadersInDisplayName; private final Set nullValues; + + @Nullable private Arguments nextArguments; - private String[] headers; + + private String @Nullable [] headers; CsvParserIterator(CsvParser csvParser, CsvFileSource csvFileSource) { this.csvParser = csvParser; @@ -137,7 +138,7 @@ public boolean hasNext() { public Arguments next() { Arguments result = this.nextArguments; advance(); - return result; + return requireNonNull(result); } private void advance() { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java index a0a4b137f4d7..3aaa3e8e7673 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java @@ -24,6 +24,7 @@ import java.util.stream.BaseStream; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; @@ -90,12 +91,12 @@ static Field findField(Class testClass, String fieldName) { .findFirst()// .orElse(null); - Preconditions.notNull(field, () -> "Could not find field named [%s] in class [%s]".formatted(resolvedFieldName, - resolvedClass.getName())); - return field; + return Preconditions.notNull(field, + () -> "Could not find field named [%s] in class [%s]".formatted(resolvedFieldName, + resolvedClass.getName())); } - private static Field validateField(Field field, Object testInstance) { + private static Field validateField(Field field, @Nullable Object testInstance) { Preconditions.condition(field.getDeclaringClass().isInstance(testInstance) || ModifierSupport.isStatic(field), () -> format("Field '%s' must be static: local @FieldSource fields must be static " + "unless the PER_CLASS @TestInstance lifecycle mode is used; " @@ -104,7 +105,7 @@ private static Field validateField(Field field, Object testInstance) { return field; } - private static Object readField(Field field, Object testInstance) { + private static Object readField(Field field, @Nullable Object testInstance) { Object value = ReflectionSupport.tryToReadFieldValue(field, testInstance).getOrThrow( cause -> new JUnitException("Could not read field [%s]".formatted(field.getName()), cause)); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index 0c112eab5a66..d4af42cebd1f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -22,6 +22,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestTemplate; @@ -53,7 +54,7 @@ protected Stream provideArguments(ParameterDeclarations par return stream(methodNames) .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName)) .map(factoryMethod -> validateFactoryMethod(factoryMethod, testInstance)) - .map(factoryMethod -> context.getExecutableInvoker().invoke(factoryMethod, testInstance)) + .map(factoryMethod -> Preconditions.notNull(context.getExecutableInvoker().invoke(factoryMethod, testInstance), () -> "@MethodSource-referenced method [%s] must not return null".formatted(factoryMethod.toGenericString()))) .flatMap(CollectionUtils::toStream) .map(ArgumentsUtils::toArguments); // @formatter:on @@ -177,7 +178,7 @@ private static boolean isTestMethod(Method candidate) { || isAnnotated(candidate, TestFactory.class); } - private static Method validateFactoryMethod(Method factoryMethod, Object testInstance) { + private static Method validateFactoryMethod(Method factoryMethod, @Nullable Object testInstance) { Preconditions.condition( factoryMethod.getDeclaringClass().isInstance(testInstance) || ReflectionUtils.isStatic(factoryMethod), () -> format("Method '%s' must be static: local factory methods must be static " diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java index e6fb99f84074..87ffaf0a38f2 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java @@ -14,6 +14,7 @@ import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.util.Preconditions; @@ -24,7 +25,7 @@ */ class NullArgumentsProvider implements ArgumentsProvider { - private static final Arguments nullArguments = arguments(new Object[] { null }); + private static final Arguments nullArguments = arguments(new @Nullable Object[] { null }); @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java index a41697ab81e3..adb442e423e5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/package-info.java @@ -5,4 +5,7 @@ * annotations. */ +@NullMarked package org.junit.jupiter.params.provider; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterInfo.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterInfo.java index 03fd6b2a811d..296a688f2545 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterInfo.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterInfo.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; @@ -68,7 +69,7 @@ public interface ParameterInfo { * {@return the closest {@code ParameterInfo} instance for the supplied * {@code ExtensionContext}; potentially {@code null}} */ - static ParameterInfo get(ExtensionContext context) { + static @Nullable ParameterInfo get(ExtensionContext context) { return context.getStore(NAMESPACE).get(KEY, ParameterInfo.class); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java index 56772a43120d..866ac5d3c0df 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/package-info.java @@ -6,4 +6,7 @@ * for arguments. */ +@NullMarked package org.junit.jupiter.params.support; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt b/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt index 7d06dc0aa071..d8a791419aef 100644 --- a/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt +++ b/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt @@ -26,4 +26,4 @@ import org.apiguardian.api.API */ @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // method is in fact not shadowed due to reified type inline fun ArgumentsAccessor.get(index: Int): T = - this.get(index, T::class.java) + this.get(index, T::class.java)!! diff --git a/junit-platform-commons/junit-platform-commons.gradle.kts b/junit-platform-commons/junit-platform-commons.gradle.kts index 3ba44841ada3..36ebd5359aa9 100644 --- a/junit-platform-commons/junit-platform-commons.gradle.kts +++ b/junit-platform-commons/junit-platform-commons.gradle.kts @@ -1,6 +1,7 @@ import junitbuild.extensions.javaModuleName plugins { + id("junitbuild.java-nullability-conventions") id("junitbuild.kotlin-library-conventions") `java-test-fixtures` } @@ -11,6 +12,7 @@ dependencies { api(platform(projects.junitBom)) compileOnlyApi(libs.apiguardian) + compileOnlyApi(libs.jspecify) compileOnly(kotlin("stdlib")) compileOnly(kotlin("reflect")) @@ -29,9 +31,11 @@ tasks.compileJava { tasks.jar { bundle { val importAPIGuardian: String by extra + val importJSpecify: String by extra bnd(""" Import-Package: \ $importAPIGuardian,\ + $importJSpecify,\ kotlin.*;resolution:="optional",\ kotlinx.*;resolution:="optional",\ * diff --git a/junit-platform-commons/src/main/java/module-info.java b/junit-platform-commons/src/main/java/module-info.java index 2cb4016429a3..092c654cdd51 100644 --- a/junit-platform-commons/src/main/java/module-info.java +++ b/junit-platform-commons/src/main/java/module-info.java @@ -16,7 +16,9 @@ module org.junit.platform.commons { requires java.logging; requires java.management; // needed by RuntimeUtils to determine input arguments + requires static transitive org.apiguardian.api; + requires static transitive org.jspecify; requires static kotlin.stdlib; requires static kotlin.reflect; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java index 7498b502c9c9..162514bec3fc 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java @@ -16,6 +16,7 @@ import java.io.Serial; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Base class for all {@link RuntimeException RuntimeExceptions} thrown @@ -29,11 +30,11 @@ public class JUnitException extends RuntimeException { @Serial private static final long serialVersionUID = 1L; - public JUnitException(String message) { + public JUnitException(@Nullable String message) { super(message); } - public JUnitException(String message, Throwable cause) { + public JUnitException(@Nullable String message, @Nullable Throwable cause) { super(message, cause); } @@ -41,7 +42,8 @@ public JUnitException(String message, Throwable cause) { * @since 1.13 */ @API(status = MAINTAINED, since = "1.13") - protected JUnitException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + protected JUnitException(@Nullable String message, @Nullable Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java index 0771766bc2de..2d9e1a5385f4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java @@ -15,6 +15,7 @@ import java.io.Serial; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Thrown if a precondition is violated. @@ -31,7 +32,7 @@ public PreconditionViolationException(String message) { super(message); } - public PreconditionViolationException(String message, Throwable cause) { + public PreconditionViolationException(String message, @Nullable Throwable cause) { super(message, cause); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java index f5f0eaf51493..1221aebb8f50 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/package-info.java @@ -2,4 +2,7 @@ * Common annotations for the JUnit Platform. */ +@NullMarked package org.junit.platform.commons.annotation; + +import org.jspecify.annotations.NullMarked; 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 04403c18a34d..31f8f172efc3 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 @@ -10,6 +10,7 @@ package org.junit.platform.commons.function; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.Objects; @@ -20,6 +21,8 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** @@ -39,7 +42,7 @@ * @since 1.4 */ @API(status = MAINTAINED, since = "1.4") -public abstract class Try { +public abstract class Try { /** * Call the supplied {@link Callable} and return a successful {@code Try} @@ -64,7 +67,7 @@ public static Try call(Callable action) { * @return a succeeded {@code Try} that contains the supplied value; never * {@code null} */ - public static Try success(V value) { + public static Try success(V value) { return new Success<>(value); } @@ -80,7 +83,7 @@ public static Try failure(Exception cause) { } // Cannot use Preconditions due to package cycle - private static T checkNotNull(T input, String title) { + private static T checkNotNull(@Nullable T input, String title) { if (input == null) { // Cannot use PreconditionViolationException due to package cycle throw new JUnitException(title + " must not be null"); @@ -149,6 +152,22 @@ private Try() { */ public abstract V get() throws Exception; + /** + * If this {@code Try} is a success, get the contained value; if this + * {@code Try} is a failure, throw the contained exception. + * + * @return the contained value, if available + * @throws Exception if this {@code Try} is a failure or the contained value + * is {@code null} + * + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + public final @NonNull V getNonNull() throws Exception { + var value = get(); + return checkNotNull(value, "value"); + } + /** * If this {@code Try} is a success, get the contained value; if this * {@code Try} is a failure, call the supplied {@link Function} with the @@ -161,6 +180,27 @@ private Try() { */ public abstract V getOrThrow(Function exceptionTransformer) throws E; + /** + * If this {@code Try} is a success, get the contained value; if this + * {@code Try} is a failure, call the supplied {@link Function} with the + * contained exception and throw the resulting {@link Exception}. + * + * @param exceptionTransformer the transformer to be called with the + * contained exception, if available; must not be {@code null} + * @return the contained value, if available and not {@code null} + * @throws E if this {@code Try} is a failure or the contained value + * is {@code null} + */ + @API(status = EXPERIMENTAL, since = "6.0") + public final @NonNull V getNonNullOrThrow( + Function exceptionTransformer) throws E { + var value = getOrThrow(exceptionTransformer); + if (value == null) { + throw exceptionTransformer.apply(null); + } + return value; + } + /** * If this {@code Try} is a success, call the supplied {@link Consumer} with * the contained value; otherwise, do nothing. @@ -198,7 +238,7 @@ private Try() { * except that a {@code Transformer} may throw an exception. */ @FunctionalInterface - public interface Transformer { + public interface Transformer { /** * Apply this transformer to the supplied value. @@ -209,7 +249,7 @@ public interface Transformer { } - private static class Success extends Try { + private static class Success extends Try { private final V value; @@ -287,7 +327,7 @@ public int hashCode() { } } - private static class Failure extends Try { + private static class Failure extends Try { private final Exception cause; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java index 8f031faf22e0..79d053801103 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java @@ -2,4 +2,7 @@ * Functional interfaces and support classes. */ +@NullMarked package org.junit.platform.commons.function; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java index 22a53d59453b..e5ac90f9d8fb 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java @@ -75,6 +75,7 @@ public Stream stream() { * @see #stream(Class) * @see #stream(Class, Level) */ + @SuppressWarnings("ConstantValue") public Stream stream(Level level) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. @@ -100,6 +101,7 @@ public Stream stream(Level level) { * @see #stream(Level) * @see #stream(Class, Level) */ + @SuppressWarnings("ConstantValue") public Stream stream(Class clazz) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. @@ -127,6 +129,7 @@ public Stream stream(Class clazz) { * @see #stream(Level) * @see #stream(Class) */ + @SuppressWarnings("ConstantValue") public Stream stream(Class clazz, Level level) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java index 09d4d3c93918..caa1dff66a17 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * The {@code Logger} API serves as a simple logging facade for @@ -38,7 +39,7 @@ public interface Logger { * *

Maps to {@link java.util.logging.Level#SEVERE} in JUL. */ - void error(Throwable throwable, Supplier messageSupplier); + void error(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at warning level. @@ -53,7 +54,7 @@ public interface Logger { * *

Maps to {@link java.util.logging.Level#WARNING} in JUL. */ - void warn(Throwable throwable, Supplier messageSupplier); + void warn(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at info level. @@ -68,7 +69,7 @@ public interface Logger { * *

Maps to {@link java.util.logging.Level#INFO} in JUL. */ - void info(Throwable throwable, Supplier messageSupplier); + void info(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at config level. @@ -83,7 +84,7 @@ public interface Logger { * *

Maps to {@link java.util.logging.Level#CONFIG} in JUL. */ - void config(Throwable throwable, Supplier messageSupplier); + void config(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at debug level. @@ -98,7 +99,7 @@ public interface Logger { * *

Maps to {@link java.util.logging.Level#FINE} in JUL. */ - void debug(Throwable throwable, Supplier messageSupplier); + void debug(@Nullable Throwable throwable, Supplier messageSupplier); /** * Log the message from the provided {@code messageSupplier} at trace level. @@ -113,6 +114,6 @@ public interface Logger { * *

Maps to {@link java.util.logging.Level#FINER} in JUL. */ - void trace(Throwable throwable, Supplier messageSupplier); + void trace(@Nullable Throwable throwable, Supplier messageSupplier); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java index c6b3ce3b44e4..ee37d83db1c9 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java @@ -19,6 +19,7 @@ import java.util.logging.LogRecord; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; /** @@ -41,6 +42,7 @@ private LoggerFactory() { * @param clazz the class for which to get the logger; never {@code null} * @return the logger */ + @SuppressWarnings("ConstantValue") public static Logger getLogger(Class clazz) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. @@ -86,7 +88,7 @@ public void error(Supplier messageSupplier) { } @Override - public void error(Throwable throwable, Supplier messageSupplier) { + public void error(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.SEVERE, throwable, messageSupplier); } @@ -96,7 +98,7 @@ public void warn(Supplier messageSupplier) { } @Override - public void warn(Throwable throwable, Supplier messageSupplier) { + public void warn(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.WARNING, throwable, messageSupplier); } @@ -106,7 +108,7 @@ public void info(Supplier messageSupplier) { } @Override - public void info(Throwable throwable, Supplier messageSupplier) { + public void info(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.INFO, throwable, messageSupplier); } @@ -116,7 +118,7 @@ public void config(Supplier messageSupplier) { } @Override - public void config(Throwable throwable, Supplier messageSupplier) { + public void config(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.CONFIG, throwable, messageSupplier); } @@ -126,7 +128,7 @@ public void debug(Supplier messageSupplier) { } @Override - public void debug(Throwable throwable, Supplier messageSupplier) { + public void debug(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.FINE, throwable, messageSupplier); } @@ -136,11 +138,11 @@ public void trace(Supplier messageSupplier) { } @Override - public void trace(Throwable throwable, Supplier messageSupplier) { + public void trace(@Nullable Throwable throwable, Supplier messageSupplier) { log(Level.FINER, throwable, messageSupplier); } - private void log(Level level, Throwable throwable, Supplier messageSupplier) { + private void log(Level level, @Nullable Throwable throwable, Supplier messageSupplier) { boolean loggable = this.julLogger.isLoggable(level); if (loggable || !listeners.isEmpty()) { LogRecord logRecord = createLogRecord(level, throwable, nullSafeGet(messageSupplier)); @@ -151,7 +153,7 @@ private void log(Level level, Throwable throwable, Supplier messageSuppl } } - private LogRecord createLogRecord(Level level, Throwable throwable, String message) { + private LogRecord createLogRecord(Level level, @Nullable Throwable throwable, @Nullable String message) { String sourceClassName = null; String sourceMethodName = null; boolean found = false; @@ -178,7 +180,7 @@ else if (found) { return logRecord; } - private static String nullSafeGet(Supplier messageSupplier) { + private static @Nullable String nullSafeGet(@Nullable Supplier<@Nullable String> messageSupplier) { return (messageSupplier != null ? messageSupplier.get() : null); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java index 75260c34a99a..9fe567592387 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/package-info.java @@ -8,4 +8,7 @@ * Use at your own risk! */ +@NullMarked package org.junit.platform.commons.logging; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java index 66e1cf2aa51c..18945b31c25e 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/package-info.java @@ -8,4 +8,7 @@ * APIs by external parties is not supported! */ +@NullMarked package org.junit.platform.commons; + +import org.jspecify.annotations.NullMarked; 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 2da05b6e2ccf..d8a0c6164fec 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 java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; @@ -70,7 +71,7 @@ private AnnotationSupport() { * @see #findRepeatableAnnotations(Optional, Class) */ @API(status = MAINTAINED, since = "1.3") - public static boolean isAnnotated(Optional element, + public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); @@ -93,7 +94,7 @@ public static boolean isAnnotated(Optional element, * @see #findAnnotation(AnnotatedElement, Class) * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ - public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { + public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); } @@ -112,8 +113,8 @@ public static boolean isAnnotated(AnnotatedElement element, Class Optional findAnnotation(Optional element, - Class annotationType) { + public static Optional findAnnotation( + @Nullable Optional element, Class annotationType) { return AnnotationUtils.findAnnotation(element, annotationType); } @@ -135,7 +136,8 @@ public static Optional findAnnotation(Optional Optional findAnnotation(AnnotatedElement element, Class annotationType) { + public static Optional findAnnotation(@Nullable AnnotatedElement element, + Class annotationType) { return AnnotationUtils.findAnnotation(element, annotationType); } @@ -173,7 +175,7 @@ public static Optional findAnnotation(AnnotatedElement @Deprecated @API(status = DEPRECATED, since = "1.12") @SuppressWarnings("deprecation") - public static Optional findAnnotation(Class clazz, Class annotationType, + public static Optional findAnnotation(@Nullable Class clazz, Class annotationType, SearchOption searchOption) { Preconditions.notNull(searchOption, "SearchOption must not be null"); @@ -214,7 +216,7 @@ public static Optional findAnnotation(Class clazz, * @see #findAnnotation(AnnotatedElement, Class) */ @API(status = EXPERIMENTAL, since = "1.12") - public static Optional findAnnotation(Class clazz, Class annotationType, + public static Optional findAnnotation(@Nullable Class clazz, Class annotationType, List> enclosingInstanceTypes) { Preconditions.notNull(enclosingInstanceTypes, "enclosingInstanceTypes must not be null"); @@ -252,8 +254,8 @@ public static Optional findAnnotation(Class clazz, * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ @API(status = MAINTAINED, since = "1.5") - public static List findRepeatableAnnotations(Optional element, - Class annotationType) { + public static List findRepeatableAnnotations( + @Nullable Optional element, Class annotationType) { return AnnotationUtils.findRepeatableAnnotations(element, annotationType); } @@ -297,7 +299,7 @@ public static List findRepeatableAnnotations(Optional< * @see java.lang.annotation.Repeatable * @see java.lang.annotation.Inherited */ - public static List findRepeatableAnnotations(AnnotatedElement element, + public static List findRepeatableAnnotations(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.findRepeatableAnnotations(element, annotationType); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index 2d4d19e32aa5..4d8b6e2e81e7 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -23,6 +23,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.util.ExceptionUtils; @@ -447,12 +448,12 @@ public static T newInstance(Class clazz, Object... args) { * @param method the method to invoke; never {@code null} * @param target the object on which to invoke the method; may be * {@code null} if the method is {@code static} - * @param args the arguments to pass to the method + * @param args the arguments to pass to the method; never {@code null} * @return the value returned by the method invocation or {@code null} * if the return type is {@code void} * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ - public static Object invokeMethod(Method method, Object target, Object... args) { + public static @Nullable Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { return ReflectionUtils.invokeMethod(method, target, args); } @@ -522,7 +523,7 @@ public static Stream streamFields(Class clazz, Predicate predic * @since 1.4 */ @API(status = MAINTAINED, since = "1.4") - public static Try tryToReadFieldValue(Field field, Object instance) { + public static Try<@Nullable Object> tryToReadFieldValue(Field field, @Nullable Object instance) { return ReflectionUtils.tryToReadFieldValue(field, instance); } @@ -547,7 +548,7 @@ public static Try tryToReadFieldValue(Field field, Object instance) { * but potentially empty if no such method could be found * @see #findMethod(Class, String, Class...) */ - public static Optional findMethod(Class clazz, String methodName, String parameterTypeNames) { + public static Optional findMethod(Class clazz, String methodName, @Nullable String parameterTypeNames) { return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java index e56570ca41c7..007ee26b07bf 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java @@ -19,6 +19,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ClassLoaderUtils; /** @@ -99,7 +100,8 @@ private ConversionSupport() { * @since 1.11 */ @SuppressWarnings("unchecked") - public static T convert(String source, Class targetType, ClassLoader classLoader) { + public static @Nullable T convert(@Nullable String source, Class targetType, + @Nullable ClassLoader classLoader) { if (source == null) { if (targetType.isPrimitive()) { throw new ConversionException( diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java index aeea2d9e119d..b7f96707a52e 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java @@ -26,6 +26,7 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -66,7 +67,7 @@ class FallbackStringToObjectConverter implements StringToObjectConverter { * This prevents the framework from repeatedly searching for things which * are already known not to exist. */ - private static final ConcurrentHashMap, Function> factoryExecutableCache // + private static final ConcurrentHashMap, Function> factoryExecutableCache // = new ConcurrentHashMap<>(64); @Override @@ -75,15 +76,16 @@ public boolean canConvertTo(Class targetType) { } @Override + @Nullable public Object convert(String source, Class targetType) throws Exception { - Function executable = findFactoryExecutable(targetType); + Function executable = findFactoryExecutable(targetType); Preconditions.condition(executable != NULL_EXECUTABLE, "Illegal state: convert() must not be called if canConvert() returned false"); return executable.apply(source); } - private static Function findFactoryExecutable(Class targetType) { + private static Function findFactoryExecutable(Class targetType) { return factoryExecutableCache.computeIfAbsent(targetType, type -> { Method factoryMethod = findFactoryMethod(type); if (factoryMethod != null) { @@ -97,7 +99,7 @@ private static Function findFactoryExecutable(Class targetTyp }); } - private static Method findFactoryMethod(Class targetType) { + private static @Nullable Method findFactoryMethod(Class targetType) { List factoryMethods = findMethods(targetType, new IsFactoryMethod(targetType), BOTTOM_UP); if (factoryMethods.size() == 1) { return factoryMethods.get(0); @@ -105,7 +107,7 @@ private static Method findFactoryMethod(Class targetType) { return null; } - private static Constructor findFactoryConstructor(Class targetType) { + private static @Nullable Constructor findFactoryConstructor(Class targetType) { List> constructors = findConstructors(targetType, new IsFactoryConstructor(targetType)); if (constructors.size() == 1) { return constructors.get(0); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java index ad16fb18edf3..a2d5cbb9322e 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java @@ -10,6 +10,7 @@ package org.junit.platform.commons.support.conversion; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ReflectionSupport; class StringToClassConverter implements StringToObjectConverter { @@ -25,7 +26,7 @@ public Object convert(String source, Class targetType) throws Exception { } @Override - public Object convert(String className, Class targetType, ClassLoader classLoader) throws Exception { + public @Nullable Object convert(String className, Class targetType, ClassLoader classLoader) throws Exception { // @formatter:off return ReflectionSupport.tryToLoadClass(className, classLoader) .getOrThrow(cause -> new ConversionException( diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java index 17aa357439ae..ceeca4d05e09 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java @@ -26,6 +26,8 @@ import java.util.UUID; import java.util.function.Function; +import org.junit.platform.commons.util.Preconditions; + class StringToCommonJavaTypesConverter implements StringToObjectConverter { private static final Map, Function> CONVERTERS; @@ -55,7 +57,9 @@ public boolean canConvertTo(Class targetType) { @Override public Object convert(String source, Class targetType) throws Exception { - return CONVERTERS.get(targetType).apply(source); + Function converter = Preconditions.notNull(CONVERTERS.get(targetType), + () -> "No registered converter for %s".formatted(targetType.getName())); + return converter.apply(source); } private static URL toURL(String url) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java index 0e544f39a3a4..fb884a84fd4b 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java @@ -30,6 +30,8 @@ import java.util.Map; import java.util.function.Function; +import org.junit.platform.commons.util.Preconditions; + class StringToJavaTimeConverter implements StringToObjectConverter { private static final Map, Function> CONVERTERS; @@ -59,7 +61,9 @@ public boolean canConvertTo(Class targetType) { @Override public Object convert(String source, Class targetType) throws Exception { - return CONVERTERS.get(targetType).apply(source); + Function converter = Preconditions.notNull(CONVERTERS.get(targetType), + () -> "No registered converter for %s".formatted(targetType.getName())); + return converter.apply(source); } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java index 21a7a0fc2e62..972cd29b07e4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java @@ -18,6 +18,8 @@ import java.util.Map; import java.util.function.Function; +import org.junit.platform.commons.util.Preconditions; + class StringToNumberConverter implements StringToObjectConverter { private static final Map, Function> CONVERTERS; @@ -44,7 +46,9 @@ public boolean canConvertTo(Class targetType) { @Override public Object convert(String source, Class targetType) { - return CONVERTERS.get(targetType).apply(source.replace("_", "")); + Function converter = Preconditions.notNull(CONVERTERS.get(targetType), + () -> "No registered converter for %s".formatted(targetType.getName())); + return converter.apply(source.replace("_", "")); } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java index ceb00e2e95d8..6c3bfffae363 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java @@ -10,6 +10,8 @@ package org.junit.platform.commons.support.conversion; +import org.jspecify.annotations.Nullable; + /** * Internal API for converting arguments of type {@link String} to a specified * target type. @@ -31,6 +33,7 @@ interface StringToObjectConverter { *

This method will only be invoked in {@link #canConvertTo(Class)} * returned {@code true} for the same target type. */ + @Nullable Object convert(String source, Class targetType) throws Exception; /** @@ -45,7 +48,7 @@ interface StringToObjectConverter { * Can be overridden by concrete implementations of this interface that need * access to the supplied {@link ClassLoader}. */ - default Object convert(String source, Class targetType, ClassLoader classLoader) throws Exception { + default @Nullable Object convert(String source, Class targetType, ClassLoader classLoader) throws Exception { return convert(source, targetType); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java index 18807d6a4e90..79ba440a7609 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java @@ -2,4 +2,7 @@ * Conversion APIs provided by the JUnit Platform. */ +@NullMarked package org.junit.platform.commons.support.conversion; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java index fae0c2a81547..68a77ec1bf22 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java @@ -8,4 +8,7 @@ * extensions with the same semantics as within the JUnit Platform itself. */ +@NullMarked package org.junit.platform.commons.support; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java index 2bc01963da99..3387e121f6e7 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java @@ -20,6 +20,7 @@ import java.util.function.BiConsumer; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; @@ -55,7 +56,7 @@ public FileVisitResult visitFileFailed(Path file, IOException ex) { } @Override - public FileVisitResult postVisitDirectory(Path dir, IOException ex) { + public FileVisitResult postVisitDirectory(Path dir, @Nullable IOException ex) { if (ex != null) { logger.warn(ex, () -> "I/O error visiting directory: " + dir); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java index 48aecf9b4896..aa18d1b587de 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java @@ -26,6 +26,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + /** * @since 1.0 */ @@ -113,7 +115,7 @@ private ManagedFileSystem retain() { return this; } - private ManagedFileSystem release() { + private @Nullable ManagedFileSystem release() { if (referenceCount.decrementAndGet() == 0) { close(); return null; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java index 769733a65aef..2da61c5d8ce9 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java @@ -2,4 +2,7 @@ * Classpath scanning APIs provided by the JUnit Platform. */ +@NullMarked package org.junit.platform.commons.support.scanning; + +import org.jspecify.annotations.NullMarked; 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 a1c16200f124..11b657a3fcb0 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 @@ -11,6 +11,7 @@ package org.junit.platform.commons.util; import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; @@ -37,6 +38,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; @@ -75,7 +77,8 @@ private AnnotationUtils() { * @see #findAnnotation(Optional, Class) * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(Optional, Class) */ - public static boolean isAnnotated(Optional element, + @SuppressWarnings("NullableOptional") + public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { return findAnnotation(element, annotationType).isPresent(); @@ -101,15 +104,16 @@ public static boolean isAnnotated(Parameter parameter, int index, Class annotationType) { + public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return findAnnotation(element, annotationType).isPresent(); } /** * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(Optional, Class) */ - public static Optional findAnnotation(Optional element, - Class annotationType) { + @SuppressWarnings({ "OptionalAssignedToNull", "NullableOptional" }) + public static Optional findAnnotation( + @Nullable Optional element, Class annotationType) { if (element == null || element.isEmpty()) { return Optional.empty(); @@ -131,14 +135,15 @@ public static Optional findAnnotation(Parameter parame /** * @see org.junit.platform.commons.support.AnnotationSupport#findAnnotation(AnnotatedElement, Class) */ - public static Optional findAnnotation(AnnotatedElement element, Class annotationType) { + public static Optional findAnnotation(@Nullable AnnotatedElement element, + Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); boolean inherited = annotationType.isAnnotationPresent(Inherited.class); return findAnnotation(element, annotationType, inherited, new HashSet<>()); } - private static Optional findAnnotation(AnnotatedElement element, Class annotationType, - boolean inherited, Set visited) { + private static Optional findAnnotation(@Nullable AnnotatedElement element, + Class annotationType, boolean inherited, Set visited) { Preconditions.notNull(annotationType, "annotationType must not be null"); @@ -223,7 +228,7 @@ private static Optional findMetaAnnotation(Class an * @since 1.8 * @see #findAnnotation(AnnotatedElement, Class) */ - public static Optional findAnnotation(Class clazz, Class annotationType, + public static Optional findAnnotation(@Nullable Class clazz, Class annotationType, boolean searchEnclosingClasses) { Preconditions.notNull(annotationType, "annotationType must not be null"); @@ -247,8 +252,9 @@ public static Optional findAnnotation(Class clazz, * @since 1.5 * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(Optional, Class) */ - public static List findRepeatableAnnotations(Optional element, - Class annotationType) { + @SuppressWarnings({ "OptionalAssignedToNull", "NullableOptional" }) + public static List findRepeatableAnnotations( + @Nullable Optional element, Class annotationType) { if (element == null || element.isEmpty()) { return Collections.emptyList(); @@ -270,7 +276,7 @@ public static List findRepeatableAnnotations(Parameter /** * @see org.junit.platform.commons.support.AnnotationSupport#findRepeatableAnnotations(AnnotatedElement, Class) */ - public static List findRepeatableAnnotations(AnnotatedElement element, + public static List findRepeatableAnnotations(@Nullable AnnotatedElement element, Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); @@ -346,14 +352,14 @@ else if (candidateAnnotationType.equals(containerType)) { cause)); Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); - found.addAll((Collection) asList(containedAnnotations)); + found.addAll((Collection) asList(requireNonNull(containedAnnotations))); } // Nested container annotation? else if (isRepeatableAnnotationContainer(candidateAnnotationType)) { Method method = ReflectionUtils.tryToGetMethod(candidateAnnotationType, "value").toOptional().get(); Annotation[] containedAnnotations = (Annotation[]) ReflectionUtils.invokeMethod(method, candidate); - for (Annotation containedAnnotation : containedAnnotations) { + for (Annotation containedAnnotation : requireNonNull(containedAnnotations)) { findRepeatableAnnotations(containedAnnotation.getClass(), annotationType, containerType, inherited, found, visited); } @@ -419,7 +425,7 @@ private static boolean isRepeatableAnnotationContainer(Class Predicate excludeMatchingClasses(String patterns) { + public static Predicate excludeMatchingClasses(@Nullable String patterns) { return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.EXCLUDE); } @@ -62,7 +63,7 @@ public static Predicate excludeMatchingClasses(String patterns) { * * @param patterns a comma-separated list of patterns */ - public static Predicate excludeMatchingClassNames(String patterns) { + public static Predicate excludeMatchingClassNames(@Nullable String patterns) { return matchingClasses(patterns, Function.identity(), FilterType.EXCLUDE); } @@ -73,7 +74,7 @@ public static Predicate excludeMatchingClassNames(String patterns) { * * @param patterns a comma-separated list of patterns */ - public static Predicate includeMatchingClasses(String patterns) { + public static Predicate includeMatchingClasses(@Nullable String patterns) { return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.INCLUDE); } @@ -83,7 +84,7 @@ public static Predicate includeMatchingClasses(String patterns) { * * @param patterns a comma-separated list of patterns */ - public static Predicate includeMatchingClassNames(String patterns) { + public static Predicate includeMatchingClassNames(@Nullable String patterns) { return matchingClasses(patterns, Function.identity(), FilterType.INCLUDE); } @@ -91,7 +92,7 @@ private enum FilterType { INCLUDE, EXCLUDE } - private static Predicate matchingClasses(String patterns, Function classNameProvider, + private static Predicate matchingClasses(@Nullable String patterns, Function classNameProvider, FilterType type) { // @formatter:off return Optional.ofNullable(patterns) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java index 9fc01426c2f6..51a6eb26f20c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java @@ -17,6 +17,7 @@ import java.util.function.Function; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Collection of utilities for working with {@link Class classes}. @@ -49,7 +50,7 @@ private ClassUtils() { * @see #nullSafeToString(Class...) * @see StringUtils#nullSafeToString(Object) */ - public static String nullSafeToString(Class clazz) { + public static String nullSafeToString(@Nullable Class clazz) { return clazz == null ? "null" : clazz.getName(); } @@ -64,7 +65,7 @@ public static String nullSafeToString(Class clazz) { * @see #nullSafeToString(Function, Class...) * @see StringUtils#nullSafeToString(Object) */ - public static String nullSafeToString(Class... classes) { + public static String nullSafeToString(@Nullable Class @Nullable... classes) { return nullSafeToString(Class::getName, classes); } @@ -83,13 +84,16 @@ public static String nullSafeToString(Class... classes) { * @see #nullSafeToString(Class...) * @see StringUtils#nullSafeToString(Object) */ - public static String nullSafeToString(Function, ? extends String> mapper, Class... classes) { + public static String nullSafeToString(Function, ? extends String> mapper, + @Nullable Class @Nullable... classes) { Preconditions.notNull(mapper, "Mapping function must not be null"); if (classes == null || classes.length == 0) { return ""; } - return stream(classes).map(clazz -> clazz == null ? "null" : mapper.apply(clazz)).collect(joining(", ")); + return stream(classes) // + .map(clazz -> clazz == null ? "null" : mapper.apply(clazz)) // + .collect(joining(", ")); } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java index 8e5a6f4d6621..ed4e255620b7 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -37,6 +37,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; @@ -66,7 +67,7 @@ private CollectionUtils() { * @throws PreconditionViolationException if the collection is {@code null} * or does not contain exactly one element */ - public static T getOnlyElement(Collection collection) { + public static T getOnlyElement(Collection collection) { Preconditions.notNull(collection, "collection must not be null"); Preconditions.condition(collection.size() == 1, () -> "collection must contain exactly one element: " + collection); @@ -82,14 +83,14 @@ public static T getOnlyElement(Collection collection) { * @since 1.11 */ @API(status = INTERNAL, since = "1.11") - public static Optional getFirstElement(Collection collection) { + public static Optional getFirstElement(Collection collection) { Preconditions.notNull(collection, "collection must not be null"); return collection.isEmpty() // ? Optional.empty() // : Optional.ofNullable(firstElement(collection)); } - private static T firstElement(Collection collection) { + private static T firstElement(Collection collection) { return collection instanceof List // ? ((List) collection).get(0) // : collection.iterator().next(); @@ -104,7 +105,7 @@ private static T firstElement(Collection collection) { * @since 1.6 */ @API(status = INTERNAL, since = "1.6") - public static Set toSet(T[] values) { + public static Set toSet(T[] values) { Preconditions.notNull(values, "values array must not be null"); if (values.length == 0) { return Collections.emptySet(); @@ -112,7 +113,7 @@ public static Set toSet(T[] values) { if (values.length == 1) { return Collections.singleton(values[0]); } - Set set = new HashSet<>(); + var set = new HashSet(); Collections.addAll(set, values); return set; } @@ -135,7 +136,7 @@ public static Set toSet(T[] values) { * @return a {@code Collector} which collects all the input elements into * an unmodifiable list, in encounter order */ - public static Collector> toUnmodifiableList() { + public static Collector> toUnmodifiableList() { return collectingAndThen(toList(), Collections::unmodifiableList); } @@ -153,7 +154,7 @@ public static Set toSet(T[] values) { * @see #toStream(Object) */ @API(status = INTERNAL, since = "1.9.1") - public static boolean isConvertibleToStream(Class type) { + public static boolean isConvertibleToStream(@Nullable Class type) { if (type == null || type == void.class) { return false; } @@ -251,7 +252,7 @@ private static Optional findIteratorMethod(Class type) { * Call the supplied action on each element of the supplied {@link List} from last to first element. */ @API(status = INTERNAL, since = "1.9.2") - public static void forEachInReverseOrder(List list, Consumer action) { + public static void forEachInReverseOrder(List list, Consumer action) { if (list.isEmpty()) { return; } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java index 9546914e2c6a..0e7470fbf099 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java @@ -16,6 +16,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Collection of utilities for working with {@link Function Functions}, @@ -43,7 +44,8 @@ private FunctionUtils() { * @param function the function to apply * @param predicate the predicate to test against the result of the function */ - public static Predicate where(Function function, Predicate predicate) { + public static Predicate where(Function function, + Predicate predicate) { Preconditions.notNull(function, "function must not be null"); Preconditions.notNull(predicate, "predicate must not be null"); return input -> predicate.test(function.apply(input)); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java index 8d897ed77751..bb8fc03d327b 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java @@ -19,6 +19,7 @@ import java.lang.reflect.Type; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.function.Try; /** @@ -29,7 +30,9 @@ @API(status = INTERNAL, since = "6.0") public class KotlinReflectionUtils { + @Nullable private static final Class kotlinMetadata; + @Nullable private static final Class kotlinCoroutineContinuation; private static final boolean kotlinReflectPresent; private static final boolean kotlinxCoroutinesPresent; @@ -89,7 +92,8 @@ public static Class[] getKotlinSuspendingFunctionParameterTypes(Method method return KotlinReflectionUtilsKt.getParameterTypes(method); } - public static Object invokeKotlinSuspendingFunction(Method method, Object target, Object[] args) { + public static @Nullable Object invokeKotlinSuspendingFunction(Method method, @Nullable Object target, + @Nullable Object[] args) { requireKotlinReflect(method); requireKotlinxCoroutines(method); return KotlinReflectionUtilsKt.invoke(method, target, args); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java index 9f20d964f471..9b069eef54d8 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java @@ -10,6 +10,7 @@ package org.junit.platform.commons.util; +import static java.util.Objects.requireNonNull; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; @@ -298,10 +299,10 @@ List scan(ModuleReference reference) { private Resource loadResourceUnchecked(String binaryName) { try { - URI uri = classLoader.getResource(binaryName).toURI(); + URI uri = requireNonNull(classLoader.getResource(binaryName)).toURI(); return new DefaultResource(binaryName, uri); } - catch (URISyntaxException e) { + catch (NullPointerException | URISyntaxException e) { throw new JUnitException("Failed to load resource with name '" + binaryName + "'.", e); } } 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 99c8ae49e6e6..c5e3e502c7c7 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 @@ -17,6 +17,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; /** @@ -50,8 +51,10 @@ private Preconditions() { * @throws PreconditionViolationException if the supplied object is {@code null} * @see #notNull(Object, Supplier) */ - public static T notNull(T object, String message) throws PreconditionViolationException { - condition(object != null, message); + public static T notNull(@Nullable T object, String message) throws PreconditionViolationException { + if (object == null) { + throw new PreconditionViolationException(message); + } return object; } @@ -64,8 +67,12 @@ public static T notNull(T object, String message) throws PreconditionViolati * @throws PreconditionViolationException if the supplied object is {@code null} * @see #condition(boolean, Supplier) */ - public static T notNull(T object, Supplier messageSupplier) throws PreconditionViolationException { - condition(object != null, messageSupplier); + public static T notNull(@Nullable T object, Supplier messageSupplier) + throws PreconditionViolationException { + + if (object == null) { + throw new PreconditionViolationException(messageSupplier.get()); + } return object; } @@ -81,8 +88,10 @@ public static T notNull(T object, Supplier messageSupplier) throws P * @see #condition(boolean, String) */ @API(status = INTERNAL, since = "1.11") - public static int[] notEmpty(int[] array, String message) throws PreconditionViolationException { - condition(array != null && array.length > 0, message); + public static int[] notEmpty(int @Nullable [] array, String message) throws PreconditionViolationException { + if (array == null || array.length == 0) { + throw new PreconditionViolationException(message); + } return array; } @@ -100,8 +109,10 @@ public static int[] notEmpty(int[] array, String message) throws PreconditionVio * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ - public static T[] notEmpty(T[] array, String message) throws PreconditionViolationException { - condition(array != null && array.length > 0, message); + public static T[] notEmpty(T @Nullable [] array, String message) throws PreconditionViolationException { + if (array == null || array.length == 0) { + throw new PreconditionViolationException(message); + } return array; } @@ -119,8 +130,12 @@ public static T[] notEmpty(T[] array, String message) throws PreconditionVio * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ - public static T[] notEmpty(T[] array, Supplier messageSupplier) throws PreconditionViolationException { - condition(array != null && array.length > 0, messageSupplier); + public static T[] notEmpty(T @Nullable [] array, Supplier messageSupplier) + throws PreconditionViolationException { + + if (array == null || array.length == 0) { + throw new PreconditionViolationException(messageSupplier.get()); + } return array; } @@ -137,10 +152,12 @@ public static T[] notEmpty(T[] array, Supplier messageSupplier) thro * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ - public static > T notEmpty(T collection, String message) + public static > T notEmpty(@Nullable T collection, String message) throws PreconditionViolationException { - condition(collection != null && !collection.isEmpty(), message); + if (collection == null || collection.isEmpty()) { + throw new PreconditionViolationException(message); + } return collection; } @@ -157,10 +174,12 @@ public static > T notEmpty(T collection, String message) * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ - public static > T notEmpty(T collection, Supplier messageSupplier) + public static > T notEmpty(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { - condition(collection != null && !collection.isEmpty(), messageSupplier); + if (collection == null || collection.isEmpty()) { + throw new PreconditionViolationException(messageSupplier.get()); + } return collection; } @@ -177,7 +196,10 @@ public static > T notEmpty(T collection, Supplier T[] containsNoNullElements(T[] array, String message) throws PreconditionViolationException { + + public static T @Nullable [] containsNoNullElements(T @Nullable [] array, String message) + throws PreconditionViolationException { + if (array != null) { Arrays.stream(array).forEach(object -> notNull(object, message)); } @@ -197,7 +219,7 @@ public static T[] containsNoNullElements(T[] array, String message) throws P * any {@code null} elements * @see #notNull(Object, String) */ - public static T[] containsNoNullElements(T[] array, Supplier messageSupplier) + public static T @Nullable [] containsNoNullElements(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { if (array != null) { @@ -219,7 +241,7 @@ public static T[] containsNoNullElements(T[] array, Supplier message * any {@code null} elements * @see #notNull(Object, String) */ - public static > T containsNoNullElements(T collection, String message) + public static > @Nullable T containsNoNullElements(@Nullable T collection, String message) throws PreconditionViolationException { if (collection != null) { @@ -241,8 +263,8 @@ public static > T containsNoNullElements(T collection, S * any {@code null} elements * @see #notNull(Object, String) */ - public static > T containsNoNullElements(T collection, Supplier messageSupplier) - throws PreconditionViolationException { + public static > @Nullable T containsNoNullElements(@Nullable T collection, + Supplier messageSupplier) throws PreconditionViolationException { if (collection != null) { collection.forEach(object -> notNull(object, messageSupplier)); @@ -262,8 +284,10 @@ public static > T containsNoNullElements(T collection, S * @throws PreconditionViolationException if the supplied string is blank * @see #notBlank(String, Supplier) */ - public static String notBlank(String str, String message) throws PreconditionViolationException { - condition(StringUtils.isNotBlank(str), message); + public static String notBlank(@Nullable String str, String message) throws PreconditionViolationException { + if (str == null || StringUtils.isBlank(str)) { + throw new PreconditionViolationException(message); + } return str; } @@ -280,8 +304,12 @@ public static String notBlank(String str, String message) throws PreconditionVio * @see StringUtils#isNotBlank(String) * @see #condition(boolean, Supplier) */ - public static String notBlank(String str, Supplier messageSupplier) throws PreconditionViolationException { - condition(StringUtils.isNotBlank(str), messageSupplier); + public static String notBlank(@Nullable String str, Supplier messageSupplier) + throws PreconditionViolationException { + + if (str == null || StringUtils.isBlank(str)) { + throw new PreconditionViolationException(messageSupplier.get()); + } return str; } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 276d8a280901..4291635ebecb 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -57,6 +57,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; @@ -379,7 +380,7 @@ public static boolean isInnerClass(Class clazz) { * @since 1.12 */ @API(status = INTERNAL, since = "1.12") - public static boolean isRecordObject(Object object) { + public static boolean isRecordObject(@Nullable Object object) { return object != null && isRecordClass(object.getClass()); } @@ -408,7 +409,7 @@ public static boolean returnsPrimitiveVoid(Method method) { * @param obj the object to test; potentially {@code null} * @return {@code true} if the object is an array */ - public static boolean isArray(Object obj) { + public static boolean isArray(@Nullable Object obj) { return (obj != null && obj.getClass().isArray()); } @@ -420,7 +421,7 @@ public static boolean isArray(Object obj) { * @since 1.3.2 */ @API(status = INTERNAL, since = "1.3.2") - public static boolean isMultidimensionalArray(Object obj) { + public static boolean isMultidimensionalArray(@Nullable Object obj) { return (obj != null && obj.getClass().isArray() && obj.getClass().getComponentType().isArray()); } @@ -480,7 +481,7 @@ public static boolean isAssignableTo(Class sourceType, Class targetType) { * @see Class#isAssignableFrom(Class) * @see #isAssignableTo(Class, Class) */ - public static boolean isAssignableTo(Object obj, Class targetType) { + public static boolean isAssignableTo(@Nullable Object obj, Class targetType) { Preconditions.notNull(targetType, "target type must not be null"); if (obj == null) { @@ -565,7 +566,7 @@ static boolean isWideningConversion(Class sourceType, Class targetType) { * @return the corresponding wrapper type or {@code null} if the * supplied type is {@code null} or not a primitive type */ - public static Class getWrapperType(Class type) { + public static @Nullable Class getWrapperType(Class type) { return primitiveToWrapperMap.get(type); } @@ -601,7 +602,7 @@ public static T newInstance(Class clazz, Object... args) { * @see #newInstance(Class, Object...) * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ - public static T newInstance(Constructor constructor, Object... args) { + public static T newInstance(Constructor constructor, @Nullable Object... args) { Preconditions.notNull(constructor, "Constructor must not be null"); try { @@ -649,7 +650,7 @@ public static Try tryToReadFieldValue(Class clazz, String fieldNa * @see #tryToReadFieldValue(Class, String, Object) */ @API(status = INTERNAL, since = "1.4") - public static Try tryToReadFieldValue(Field field) { + public static Try<@Nullable Object> tryToReadFieldValue(Field field) { return tryToReadFieldValue(field, null); } @@ -659,7 +660,7 @@ public static Try tryToReadFieldValue(Field field) { * @see #tryToReadFieldValue(Class, String, Object) */ @API(status = INTERNAL, since = "1.4") - public static Try tryToReadFieldValue(Field field, Object instance) { + public static Try<@Nullable Object> tryToReadFieldValue(Field field, @Nullable Object instance) { Preconditions.notNull(field, "Field must not be null"); Preconditions.condition((instance != null || isStatic(field)), () -> String.format("Cannot read non-static field [%s] on a null instance.", field)); @@ -678,7 +679,7 @@ public static Try tryToReadFieldValue(Field field, Object instance) { * @return an immutable list of the values of the specified fields; never * {@code null} but may be empty or contain {@code null} entries */ - public static List readFieldValues(List fields, Object instance) { + public static List readFieldValues(List fields, @Nullable Object instance) { return readFieldValues(fields, instance, field -> true); } @@ -695,7 +696,8 @@ public static List readFieldValues(List fields, Object instance) * @return an immutable list of the values of the specified fields; never * {@code null} but may be empty or contain {@code null} entries */ - public static List readFieldValues(List fields, Object instance, Predicate predicate) { + public static List readFieldValues(List fields, @Nullable Object instance, + Predicate predicate) { Preconditions.notNull(fields, "fields list must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); @@ -712,7 +714,7 @@ public static List readFieldValues(List fields, Object instance, /** * @see org.junit.platform.commons.support.ReflectionSupport#invokeMethod(Method, Object, Object...) */ - public static Object invokeMethod(Method method, Object target, Object... args) { + public static @Nullable Object invokeMethod(Method method, @Nullable Object target, @Nullable Object... args) { Preconditions.notNull(method, "Method must not be null"); Preconditions.condition((target != null || isStatic(method)), () -> String.format("Cannot invoke non-static method [%s] on a null target.", method.toGenericString())); @@ -749,7 +751,7 @@ public static Try> tryToLoadClass(String name) { */ @API(status = INTERNAL, since = "1.11") public static Class loadRequiredClass(String name, ClassLoader classLoader) throws JUnitException { - return tryToLoadClass(name, classLoader).getOrThrow( + return tryToLoadClass(name, classLoader).getNonNullOrThrow( cause -> new JUnitException(format("Could not load class [%s]", name), cause)); } @@ -881,7 +883,8 @@ public static String getFullyQualifiedMethodName(Class clazz, Method method) * @param parameterTypes the parameter types of the method; may be {@code null} or empty * @return fully qualified method name; never {@code null} */ - public static String getFullyQualifiedMethodName(Class clazz, String methodName, Class... parameterTypes) { + public static String getFullyQualifiedMethodName(Class clazz, String methodName, + Class @Nullable... parameterTypes) { Preconditions.notNull(clazz, "Class must not be null"); return getFullyQualifiedMethodName(clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes)); @@ -1413,7 +1416,7 @@ public static boolean isMethodPresent(Class clazz, Predicate predicat * @since 1.4 */ @API(status = INTERNAL, since = "1.4") - public static Try tryToGetMethod(Class clazz, String methodName, Class... parameterTypes) { + public static Try tryToGetMethod(Class clazz, String methodName, Class @Nullable... parameterTypes) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); @@ -1432,7 +1435,7 @@ public static Try tryToGetMethod(Class clazz, String methodName, Clas * @since 1.11 */ @API(status = INTERNAL, since = "1.11") - public static Method getInterfaceMethodIfPossible(Method method, Class targetClass) { + public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class targetClass) { if (!isPublic(method) || method.getDeclaringClass().isInterface()) { return method; } @@ -1470,15 +1473,17 @@ private static Method findInterfaceMethodIfPossible(Method method, Class[] pa /** * @see org.junit.platform.commons.support.ReflectionSupport#findMethod(Class, String, String) */ - public static Optional findMethod(Class clazz, String methodName, String parameterTypeNames) { + public static Optional findMethod(Class clazz, String methodName, @Nullable String parameterTypeNames) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); return findMethod(clazz, methodName, resolveParameterTypes(clazz, methodName, parameterTypeNames)); } @API(status = INTERNAL, since = "1.10") - public static Class[] resolveParameterTypes(Class clazz, String methodName, String parameterTypeNames) { - if (StringUtils.isBlank(parameterTypeNames)) { + public static Class[] resolveParameterTypes(Class clazz, String methodName, + @Nullable String parameterTypeNames) { + + if (parameterTypeNames == null || StringUtils.isBlank(parameterTypeNames)) { return EMPTY_CLASS_ARRAY; } @@ -1495,7 +1500,7 @@ private static Class loadRequiredParameterType(Class clazz, String methodN // @formatter:off return tryToLoadClass(typeName, classLoader) - .getOrThrow(cause -> new JUnitException( + .getNonNullOrThrow(cause -> new JUnitException( String.format("Failed to load parameter type [%s] for method [%s] in class [%s].", typeName, methodName, clazz.getName()), cause)); // @formatter:on @@ -1971,7 +1976,7 @@ private static void getAllAssignmentCompatibleClasses(Class clazz, Set clazz) { + private static boolean isSearchable(@Nullable Class clazz) { return (clazz != null && clazz != Object.class); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java index 490d5c741c89..192e3f2c4d6b 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java @@ -61,7 +61,7 @@ static Optional> getInputArguments() { // See https://github.com/junit-team/junit4/pull/1187 try { Object bean = managementFactoryClass.get().getMethod("getRuntimeMXBean").invoke(null); - Class mx = ReflectionUtils.tryToLoadClass("java.lang.management.RuntimeMXBean").get(); + Class mx = ReflectionUtils.tryToLoadClass("java.lang.management.RuntimeMXBean").getNonNull(); @SuppressWarnings("unchecked") List args = (List) mx.getMethod("getInputArguments").invoke(bean); return Optional.of(args); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java index db209116b615..d9f179b6b381 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java @@ -45,6 +45,10 @@ private ServiceLoaderUtils() { */ public static Stream filter(ServiceLoader serviceLoader, Predicate> providerPredicate) { + + Preconditions.notNull(serviceLoader, "serviceLoader must not be null"); + Preconditions.notNull(providerPredicate, "providerPredicate must not be null"); + // @formatter:off return serviceLoader .stream() 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 18949e91eb9c..cdef22876436 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 @@ -20,6 +20,7 @@ import java.util.regex.Pattern; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Collection of utilities for working with {@link String Strings}, @@ -67,7 +68,7 @@ private StringUtils() { * @return {@code true} if the string is blank * @see #isNotBlank(String) */ - public static boolean isBlank(String str) { + public static boolean isBlank(@Nullable String str) { return (str == null || str.trim().isEmpty()); } @@ -79,7 +80,7 @@ public static boolean isBlank(String str) { * @return {@code true} if the string is not blank * @see #isBlank(String) */ - public static boolean isNotBlank(String str) { + public static boolean isNotBlank(@Nullable String str) { return !isBlank(str); } @@ -91,7 +92,7 @@ public static boolean isNotBlank(String str) { * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ - public static boolean containsWhitespace(String str) { + public static boolean containsWhitespace(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isWhitespace); } @@ -105,7 +106,7 @@ public static boolean containsWhitespace(String str) { * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ - public static boolean doesNotContainWhitespace(String str) { + public static boolean doesNotContainWhitespace(@Nullable String str) { return !containsWhitespace(str); } @@ -117,7 +118,7 @@ public static boolean doesNotContainWhitespace(String str) { * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ - public static boolean containsIsoControlCharacter(String str) { + public static boolean containsIsoControlCharacter(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isISOControl); } @@ -131,7 +132,7 @@ public static boolean containsIsoControlCharacter(String str) { * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ - public static boolean doesNotContainIsoControlCharacter(String str) { + public static boolean doesNotContainIsoControlCharacter(@Nullable String str) { return !containsIsoControlCharacter(str); } @@ -157,7 +158,7 @@ public static boolean doesNotContainIsoControlCharacter(String str) { * @see Arrays#deepToString(Object[]) * @see ClassUtils#nullSafeToString(Class...) */ - public static String nullSafeToString(Object obj) { + public static String nullSafeToString(@Nullable Object obj) { if (obj == null) { return "null"; } @@ -221,7 +222,7 @@ public static String nullSafeToString(Object obj) { * @see #nullSafeToString(Object) * @see ClassUtils#nullSafeToString(Class...) */ - public static String defaultToString(Object obj) { + public static String defaultToString(@Nullable Object obj) { if (obj == null) { return "null"; } @@ -239,7 +240,7 @@ public static String defaultToString(Object obj) { * @since 1.4 */ @API(status = INTERNAL, since = "1.4") - public static String replaceIsoControlCharacters(String str, String replacement) { + 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); } @@ -254,7 +255,7 @@ public static String replaceIsoControlCharacters(String str, String replacement) * @since 1.4 */ @API(status = INTERNAL, since = "1.4") - public static String replaceWhitespaceCharacters(String str, String replacement) { + 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-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java index eabaf3dcc09a..97bfb1a6cc30 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java @@ -17,6 +17,7 @@ import java.util.List; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Simple builder for generating strings in custom implementations of @@ -50,13 +51,13 @@ public ToStringBuilder(String typeName) { this.typeName = Preconditions.notNull(typeName, "Type name must not be null"); } - public ToStringBuilder append(String name, Object value) { + public ToStringBuilder append(String name, @Nullable Object value) { Preconditions.notBlank(name, "Name must not be null or blank"); this.values.add(name + " = " + toString(value)); return this; } - private String toString(Object obj) { + private String toString(@Nullable Object obj) { return (obj instanceof CharSequence) ? ("'" + obj + "'") : StringUtils.nullSafeToString(obj); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java index 23589c2a1672..7799476137a1 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Internal utilities for working with unrecoverable exceptions. @@ -47,7 +48,7 @@ private UnrecoverableExceptions() { *

If the supplied {@code exception} is not unrecoverable, this * method does nothing. */ - public static void rethrowIfUnrecoverable(Throwable exception) { + public static void rethrowIfUnrecoverable(@Nullable Throwable exception) { if (exception instanceof OutOfMemoryError) { throw ExceptionUtils.throwAsUncheckedException(exception); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java index 119940686c89..3d9e974b50f9 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/package-info.java @@ -8,4 +8,7 @@ * Use at your own risk! */ +@NullMarked package org.junit.platform.commons.util; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java index e0ae6d6c762e..ca1474c2e372 100644 --- a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java +++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java @@ -21,6 +21,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.jspecify.annotations.Nullable; + public class ConcurrencyTestingUtils { public static void executeConcurrently(int threads, Runnable action) throws Exception { @@ -30,7 +32,7 @@ public static void executeConcurrently(int threads, Runnable action) throws Exce }); } - public static List executeConcurrently(int threads, Callable action) throws Exception { + public static List executeConcurrently(int threads, Callable<@Nullable T> action) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(threads); try { CountDownLatch latch = new CountDownLatch(threads); diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/package-info.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/package-info.java new file mode 100644 index 000000000000..72143b56db41 --- /dev/null +++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package org.junit.platform.commons.test; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts index 4bdc51103c64..1ef341f586ff 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -54,10 +54,12 @@ tasks { bundle { val importAPIGuardian: String by extra + val importJSpecify: String by extra bnd(""" # Customize the imports because this is an aggregate jar Import-Package: \ $importAPIGuardian,\ + $importJSpecify,\ kotlin.*;resolution:="optional",\ kotlinx.*;resolution:="optional",\ * diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index 78c5f19c0e9d..0dd0f1b66209 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -1,8 +1,11 @@ import junitbuild.extensions.javaModuleName import junitbuild.java.UpdateJarAction +import net.ltgt.gradle.errorprone.errorprone +import net.ltgt.gradle.nullaway.nullaway plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") id("junitbuild.shadow-conventions") } @@ -13,6 +16,7 @@ dependencies { api(projects.junitPlatformReporting) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) shadowed(libs.picocli) @@ -27,6 +31,13 @@ tasks { "--add-modules", "info.picocli", "--add-reads", "${javaModuleName}=info.picocli" )) + options.errorprone.nullaway { + excludedFieldAnnotations.addAll( + "picocli.CommandLine.ArgGroup", + "picocli.CommandLine.Mixin", + "picocli.CommandLine.Spec", + ) + } } javadoc { (options as StandardJavadocDocletOptions).apply { diff --git a/junit-platform-console/src/main/java/module-info.java b/junit-platform-console/src/main/java/module-info.java index 166aff5ee795..0fe1a03f0cf7 100644 --- a/junit-platform-console/src/main/java/module-info.java +++ b/junit-platform-console/src/main/java/module-info.java @@ -15,7 +15,10 @@ * @provides java.util.spi.ToolProvider */ module org.junit.platform.console { + requires static org.apiguardian.api; + requires static org.jspecify; + requires org.junit.platform.commons; requires org.junit.platform.engine; requires org.junit.platform.launcher; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java index 87a202e0560a..2044347f8265 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * @since 1.10 @@ -40,14 +41,16 @@ public static CommandResult failure() { return create(FAILURE, null); } - public static CommandResult create(int exitCode, T value) { + public static CommandResult create(int exitCode, @Nullable T value) { return new CommandResult<>(exitCode, value); } private final int exitCode; + + @Nullable private final T value; - private CommandResult(int exitCode, T value) { + private CommandResult(int exitCode, @Nullable T value) { this.exitCode = exitCode; this.value = value; } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java index 2f592de24919..ca6c4d0281c8 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java @@ -16,6 +16,7 @@ import java.nio.file.Path; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.platform.console.tasks.ConsoleTestExecutor; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -23,6 +24,7 @@ import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; @Command(// name = "execute", // @@ -93,10 +95,11 @@ public int getExitCode() { static class ReportingOptions { - @CommandLine.Option(names = "--fail-if-no-tests", description = "Fail and return exit status code 2 if no tests are found.") + @Option(names = "--fail-if-no-tests", description = "Fail and return exit status code 2 if no tests are found.") private boolean failIfNoTests; // no single-dash equivalent: was introduced in 5.3-M1 - @CommandLine.Option(names = "--reports-dir", paramLabel = "DIR", description = "Enable report output into a specified local directory (will be created if it does not exist).") + @Nullable + @Option(names = "--reports-dir", paramLabel = "DIR", description = "Enable report output into a specified local directory (will be created if it does not exist).") private Path reportsDir; Optional getReportsDir() { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java index b04249508a16..90bbf2d42c6f 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java @@ -10,8 +10,11 @@ package org.junit.platform.console.options; +import static java.util.Objects.requireNonNull; + import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.platform.console.tasks.ConsoleTestExecutor; import picocli.CommandLine; @@ -56,6 +59,7 @@ class MainCommand implements Runnable, IExitCodeGenerator { @Spec CommandSpec commandSpec; + @Nullable CommandResult commandResult; MainCommand(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { @@ -79,7 +83,7 @@ else if (versionRequested) { @Override public int getExitCode() { - return commandResult.getExitCode(); + return requireNonNull(commandResult).getExitCode(); } CommandResult run(String[] args, diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java index 73f4fc4a2416..235588768f12 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java @@ -16,6 +16,7 @@ import java.util.Locale; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * @since 1.10 @@ -28,11 +29,18 @@ public class TestConsoleOutputOptions { static final Theme DEFAULT_THEME = Theme.valueOf(ConsoleUtils.charset()); private boolean ansiColorOutputDisabled; + + @Nullable private Path colorPalettePath; + private boolean isSingleColorPalette; private Details details = DEFAULT_DETAILS; private Theme theme = DEFAULT_THEME; + + @Nullable private Path stdoutPath; + + @Nullable private Path stderrPath; public boolean isAnsiColorOutputDisabled() { @@ -43,11 +51,11 @@ public void setAnsiColorOutputDisabled(boolean ansiColorOutputDisabled) { this.ansiColorOutputDisabled = ansiColorOutputDisabled; } - public Path getColorPalettePath() { + public @Nullable Path getColorPalettePath() { return colorPalettePath; } - public void setColorPalettePath(Path colorPalettePath) { + public void setColorPalettePath(@Nullable Path colorPalettePath) { this.colorPalettePath = colorPalettePath; } @@ -76,22 +84,22 @@ public void setTheme(Theme theme) { } @API(status = INTERNAL, since = "1.13") - public Path getStdoutPath() { + public @Nullable Path getStdoutPath() { return this.stdoutPath; } @API(status = INTERNAL, since = "1.13") - public void setStdoutPath(Path stdoutPath) { + public void setStdoutPath(@Nullable Path stdoutPath) { this.stdoutPath = stdoutPath; } @API(status = INTERNAL, since = "1.13") - public Path getStderrPath() { + public @Nullable Path getStderrPath() { return this.stderrPath; } @API(status = INTERNAL, since = "1.13") - public void setStderrPath(Path stderrPath) { + public void setStderrPath(@Nullable Path stderrPath) { this.stderrPath = stderrPath; } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java index 3faa6cc3fe48..54d3cbd34a29 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java @@ -16,6 +16,8 @@ import java.nio.file.Path; +import org.jspecify.annotations.Nullable; + import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Option; @@ -26,6 +28,7 @@ class TestConsoleOutputOptionsMixin { static class ConsoleOutputOptions { + @Nullable @Option(names = "--color-palette", paramLabel = "FILE", description = "Specify a path to a properties file to customize ANSI style of output (not supported by all terminals).") private Path colorPalette; @@ -41,9 +44,11 @@ static class ConsoleOutputOptions { + "Use one of: ${COMPLETION-CANDIDATES}. Default is detected based on default character encoding.") private final Theme theme = DEFAULT_THEME; + @Nullable @Option(names = "--redirect-stdout", paramLabel = "FILE", description = "Redirect test output to stdout to a file.") private Path stdout; + @Nullable @Option(names = "--redirect-stderr", paramLabel = "FILE", description = "Redirect test output to stderr to a file.") private Path stderr; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java index 0345dde65603..030cc1475b2f 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java @@ -24,6 +24,7 @@ import java.util.Map; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.ClassSelector; @@ -46,6 +47,8 @@ public class TestDiscoveryOptions { private boolean scanClasspath; private List additionalClasspathEntries = emptyList(); + + @Nullable private List selectedClasspathEntries = emptyList(); private boolean scanModulepath; @@ -104,11 +107,11 @@ public void setAdditionalClasspathEntries(List additionalClasspathEntries) this.additionalClasspathEntries = additionalClasspathEntries; } - public List getSelectedClasspathEntries() { + public @Nullable List getSelectedClasspathEntries() { return this.selectedClasspathEntries; } - public void setSelectedClasspathEntries(List selectedClasspathEntries) { + public void setSelectedClasspathEntries(@Nullable List selectedClasspathEntries) { this.selectedClasspathEntries = selectedClasspathEntries; } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java index 0c59318a95a8..18ff561a96dc 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; @@ -50,6 +51,7 @@ class TestDiscoveryOptionsMixin { static class SelectorOptions { + @Nullable @Option(names = { "--scan-classpath", "--scan-class-path" }, converter = ClasspathEntriesConverter.class, paramLabel = "PATH", arity = "0..1", description = "Scan all directories on the classpath or explicit classpath roots. " // + "Without arguments, only directories on the system classpath as well as additional classpath " // diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java index babb46e295da..f06150fae4b5 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/package-info.java @@ -2,4 +2,7 @@ * Configuration options for JUnit's console launcher. */ +@NullMarked package org.junit.platform.console.options; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java b/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java index 73bc1b41dceb..3b0816fca102 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/package-info.java @@ -2,4 +2,7 @@ * Support for launching the JUnit Platform from the console. */ +@NullMarked package org.junit.platform.console; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java index d85bed36ca35..fd687ba632be 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java @@ -82,12 +82,14 @@ private static List createClasspathRootSelectors(TestDisc } private static Set determineClasspathRoots(TestDiscoveryOptions options) { - if (options.getSelectedClasspathEntries().isEmpty()) { + var selectedClasspathEntries = Preconditions.notNull(options.getSelectedClasspathEntries(), + () -> "No classpath entries selected"); + if (selectedClasspathEntries.isEmpty()) { Set rootDirs = new LinkedHashSet<>(ReflectionUtils.getAllClasspathRootDirectories()); rootDirs.addAll(options.getExistingAdditionalClasspathEntries()); return rootDirs; } - return new LinkedHashSet<>(options.getSelectedClasspathEntries()); + return new LinkedHashSet<>(selectedClasspathEntries); } private static void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDiscoveryOptions options, diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/StandardStreamsHandler.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/StandardStreamsHandler.java index 50bcb1309054..9cb91fce9418 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/StandardStreamsHandler.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/StandardStreamsHandler.java @@ -15,12 +15,15 @@ import java.nio.file.Files; import java.nio.file.Path; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; class StandardStreamsHandler implements AutoCloseable { + @Nullable private PrintStream stdout; + @Nullable private PrintStream stderr; public StandardStreamsHandler() { @@ -39,7 +42,7 @@ public StandardStreamsHandler() { * @param stderrPath the file path for standard error, or {@code null} to * indicate no redirection */ - public void redirectStandardStreams(Path stdoutPath, Path stderrPath) { + public void redirectStandardStreams(@Nullable Path stdoutPath, @Nullable Path stderrPath) { if (isSameFile(stdoutPath, stderrPath)) { try { PrintStream commonStream = new PrintStream(Files.newOutputStream(stdoutPath), true); @@ -92,7 +95,7 @@ public void close() { } } - private static boolean isSameFile(Path path1, Path path2) { + private static boolean isSameFile(@Nullable Path path1, @Nullable Path path2) { if (path1 == null || path2 == null) { return false; } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java index e44b4bec561e..b549494b0507 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java @@ -10,12 +10,14 @@ package org.junit.platform.console.tasks; +import static java.util.Objects.requireNonNull; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; @@ -29,6 +31,8 @@ class TestFeedPrintingListener implements DetailsPrintingListener { private final PrintWriter out; private final ColorPalette colorPalette; + + @Nullable private TestPlan testPlan; TestFeedPrintingListener(PrintWriter out, ColorPalette colorPalette) { @@ -94,7 +98,7 @@ private List collectDisplayNames(UniqueId uniqueId) { int size = uniqueId.getSegments().size(); List displayNames = new ArrayList<>(size); for (int i = 0; i < size; i++) { - displayNames.add(0, testPlan.getTestIdentifier(uniqueId).getDisplayName()); + displayNames.add(0, requireNonNull(testPlan).getTestIdentifier(uniqueId).getDisplayName()); if (i < size - 1) { uniqueId = uniqueId.removeLastSegment(); } 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 e60a07146fa2..55e05009a24f 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,10 +10,13 @@ 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; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; @@ -28,9 +31,16 @@ class TreeNode { private final String caption; private final long creation; long duration; + + @Nullable private String reason; + + @Nullable private TestIdentifier identifier; + + @Nullable private TestExecutionResult result; + final Queue reports = new ConcurrentLinkedQueue<>(); final Queue files = new ConcurrentLinkedQueue<>(); final Queue children = new ConcurrentLinkedQueue<>(); @@ -94,6 +104,6 @@ static String createCaption(String displayName) { boolean normal = displayName.length() <= 80; String caption = normal ? displayName : displayName.substring(0, 80) + "..."; String whites = StringUtils.replaceWhitespaceCharacters(caption, " "); - return StringUtils.replaceIsoControlCharacters(whites, "."); + return requireNonNull(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 6e6201031ca0..a967498be2ce 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,6 +10,7 @@ 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; @@ -128,7 +129,7 @@ private void printThrowable(String indent, TestExecutionResult result) { if (StringUtils.isBlank(message)) { message = throwable.toString(); } - printMessage(Style.FAILED, indent, message); + printMessage(Style.FAILED, indent, requireNonNull(message)); } private void printReportEntry(String indent, ReportEntry reportEntry) { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java index 49ec53d48ec8..d8d7130e400a 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java @@ -10,10 +10,14 @@ package org.junit.platform.console.tasks; +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElse; + import java.io.PrintWriter; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.jspecify.annotations.Nullable; import org.junit.platform.console.options.Theme; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; @@ -28,20 +32,23 @@ class TreePrintingListener implements DetailsPrintingListener { private final Map nodesByUniqueId = new ConcurrentHashMap<>(); - private TreeNode root; private final TreePrinter treePrinter; + @Nullable + private TreeNode root; + TreePrintingListener(PrintWriter out, ColorPalette colorPalette, Theme theme) { this.treePrinter = new TreePrinter(out, theme, colorPalette); } private void addNode(TestIdentifier testIdentifier, TreeNode node) { nodesByUniqueId.put(testIdentifier.getUniqueIdObject(), node); - testIdentifier.getParentIdObject().map(nodesByUniqueId::get).orElse(root).addChild(node); + TreeNode parent = testIdentifier.getParentIdObject().map(nodesByUniqueId::get).orElse(null); + requireNonNullElse(parent, root).addChild(node); } private TreeNode getNode(TestIdentifier testIdentifier) { - return nodesByUniqueId.get(testIdentifier.getUniqueIdObject()); + return requireNonNull(nodesByUniqueId.get(testIdentifier.getUniqueIdObject())); } @Override @@ -51,7 +58,7 @@ public void testPlanExecutionStarted(TestPlan testPlan) { @Override public void testPlanExecutionFinished(TestPlan testPlan) { - treePrinter.print(root); + treePrinter.print(requireNonNull(root)); } @Override diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java index bcfe3d83e71c..39f268bb129d 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/package-info.java @@ -2,4 +2,7 @@ * Internal execution tasks for JUnit's console launcher. */ +@NullMarked package org.junit.platform.console.tasks; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts index 416b227b00c1..09282c6e49f3 100644 --- a/junit-platform-engine/junit-platform-engine.gradle.kts +++ b/junit-platform-engine/junit-platform-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") `java-test-fixtures` } @@ -11,6 +12,7 @@ dependencies { api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) + compileOnlyApi(libs.jspecify) testImplementation(libs.assertj) diff --git a/junit-platform-engine/src/main/java/module-info.java b/junit-platform-engine/src/main/java/module-info.java index 4727b0182960..d23c9872dad9 100644 --- a/junit-platform-engine/src/main/java/module-info.java +++ b/junit-platform-engine/src/main/java/module-info.java @@ -17,7 +17,10 @@ * @since 1.0 */ module org.junit.platform.engine { + requires static transitive org.apiguardian.api; + requires static org.jspecify; + requires transitive org.junit.platform.commons; requires transitive org.opentest4j; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java index b447c70f642b..d721066ce792 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java @@ -17,6 +17,7 @@ import java.util.function.Function; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; @@ -116,7 +117,7 @@ public interface ConfigurationParameters { * @see #CONFIG_FILE_NAME */ @API(status = STABLE, since = "1.3") - default Optional get(String key, Function transformer) { + default Optional get(String key, Function transformer) { Preconditions.notNull(transformer, "transformer must not be null"); return get(key).map(input -> { try { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultDiscoveryIssue.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultDiscoveryIssue.java index 055aa4029191..f722facb7fa0 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultDiscoveryIssue.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DefaultDiscoveryIssue.java @@ -13,6 +13,7 @@ import java.util.Objects; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** @@ -22,7 +23,11 @@ final class DefaultDiscoveryIssue implements DiscoveryIssue { private final Severity severity; private final String message; + + @Nullable private final TestSource source; + + @Nullable private final Throwable cause; DefaultDiscoveryIssue(Builder builder) { @@ -86,7 +91,11 @@ static class Builder implements DiscoveryIssue.Builder { private final Severity severity; private final String message; + + @Nullable private TestSource source; + + @Nullable public Throwable cause; Builder(Severity severity, String message) { @@ -95,13 +104,13 @@ static class Builder implements DiscoveryIssue.Builder { } @Override - public Builder source(TestSource source) { + public Builder source(@Nullable TestSource source) { this.source = source; return this; } @Override - public Builder cause(Throwable cause) { + public Builder cause(@Nullable Throwable cause) { this.cause = cause; return this; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryIssue.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryIssue.java index 287523181921..adc0e8005b63 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryIssue.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryIssue.java @@ -16,6 +16,7 @@ import java.util.function.UnaryOperator; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -138,7 +139,7 @@ default Builder source(Optional source) { * @param source the {@link TestSource} for the {@code DiscoveryIssue}; * may be {@code null} */ - Builder source(TestSource source); + Builder source(@Nullable TestSource source); /** * Set the {@link Throwable} that caused the {@code DiscoveryIssue}. @@ -157,7 +158,7 @@ default Builder cause(Optional cause) { * @param cause the {@link Throwable} that caused the * {@code DiscoveryIssue}; may be {@code null} */ - Builder cause(Throwable cause); + Builder cause(@Nullable Throwable cause); /** * Build the {@code DiscoveryIssue}. diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java index 3bdacace4401..3e429b6c4fb9 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -46,7 +46,8 @@ public interface EngineDiscoveryRequest { * Get the {@link DiscoverySelector DiscoverySelectors} for this request, * filtered by a particular type. * - * @param selectorType the type of {@link DiscoverySelector} to filter by + * @param selectorType the type of {@link DiscoverySelector} to filter by; + * never {@code null} * @return all selectors of this request that are instances of * {@code selectorType}; never {@code null} but potentially empty */ @@ -59,7 +60,8 @@ public interface EngineDiscoveryRequest { *

The returned filters are to be combined using AND semantics, i.e. all * of them have to include a resource for it to end up in the test plan. * - * @param filterType the type of {@link DiscoveryFilter} to filter by + * @param filterType the type of {@link DiscoveryFilter} to filter by; + * never {@code null} * @return all filters of this request that are instances of * {@code filterType}; never {@code null} but potentially empty */ diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java index 1267551da486..7bff6ea78605 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java @@ -16,6 +16,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.reporting.OutputDirectoryProvider; @@ -41,7 +42,11 @@ public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; + + @Nullable private final OutputDirectoryProvider outputDirectoryProvider; + + @Nullable private final NamespacedHierarchicalStore requestLevelStore; @Deprecated @@ -52,8 +57,8 @@ public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListen } private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, - ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider, - NamespacedHierarchicalStore requestLevelStore) { + ConfigurationParameters configurationParameters, @Nullable OutputDirectoryProvider outputDirectoryProvider, + @Nullable NamespacedHierarchicalStore requestLevelStore) { this.rootTestDescriptor = Preconditions.notNull(rootTestDescriptor, "rootTestDescriptor must not be null"); this.engineExecutionListener = Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java index b2bfae49122b..eabfcfe20815 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** @@ -32,7 +33,7 @@ public class FilterResult { * @param reason the reason why the filtered object was included * @return an included {@code FilterResult} with the given reason */ - public static FilterResult included(String reason) { + public static FilterResult included(@Nullable String reason) { return new FilterResult(true, reason); } @@ -42,14 +43,14 @@ public static FilterResult included(String reason) { * @param reason the reason why the filtered object was excluded * @return an excluded {@code FilterResult} with the given reason */ - public static FilterResult excluded(String reason) { + public static FilterResult excluded(@Nullable String reason) { return new FilterResult(false, reason); } /** * Factory for creating filter results based on the condition given. * - * @param included whether or not the filtered object should be included + * @param included whether the filtered object should be included * @return a valid {@code FilterResult} for the given condition */ public static FilterResult includedIf(boolean included) { @@ -59,13 +60,13 @@ public static FilterResult includedIf(boolean included) { /** * Factory for creating filter results based on the condition given. * - * @param included whether or not the filtered object should be included + * @param included whether the filtered object should be included * @param inclusionReasonSupplier supplier for the reason in case of inclusion * @param exclusionReasonSupplier supplier for the reason in case of exclusion * @return a valid {@code FilterResult} for the given condition */ - public static FilterResult includedIf(boolean included, Supplier inclusionReasonSupplier, - Supplier exclusionReasonSupplier) { + public static FilterResult includedIf(boolean included, Supplier<@Nullable String> inclusionReasonSupplier, + Supplier<@Nullable String> exclusionReasonSupplier) { return included ? included(inclusionReasonSupplier.get()) : excluded(exclusionReasonSupplier.get()); } @@ -73,7 +74,7 @@ public static FilterResult includedIf(boolean included, Supplier inclusi private final Optional reason; - private FilterResult(boolean included, String reason) { + private FilterResult(boolean included, @Nullable String reason) { this.included = included; this.reason = Optional.ofNullable(reason); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java index 105bf9d09e7d..2b2b1a2f0951 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** @@ -86,9 +87,11 @@ public static SelectorResolutionResult failed(Throwable throwable) { } private final Status status; + + @Nullable private final Throwable throwable; - private SelectorResolutionResult(Status status, Throwable throwable) { + private SelectorResolutionResult(Status status, @Nullable Throwable throwable) { this.status = status; this.throwable = throwable; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java index f3113a41be5a..e07b0d52ce89 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java @@ -22,6 +22,7 @@ import java.util.function.UnaryOperator; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; /** @@ -99,7 +100,7 @@ default String getLegacyReportingName() { * * @param parent the new parent of this descriptor; may be {@code null}. */ - void setParent(TestDescriptor parent); + void setParent(@Nullable TestDescriptor parent); /** * Get the immutable set of children of this descriptor. diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java index 4bd69d935d05..2052cf360459 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; @@ -77,7 +78,7 @@ public static TestExecutionResult successful() { * {@code null} * @return the {@code TestExecutionResult}; never {@code null} */ - public static TestExecutionResult aborted(Throwable throwable) { + public static TestExecutionResult aborted(@Nullable Throwable throwable) { return new TestExecutionResult(ABORTED, throwable); } @@ -89,14 +90,16 @@ public static TestExecutionResult aborted(Throwable throwable) { * {@code null} * @return the {@code TestExecutionResult}; never {@code null} */ - public static TestExecutionResult failed(Throwable throwable) { + public static TestExecutionResult failed(@Nullable Throwable throwable) { return new TestExecutionResult(FAILED, throwable); } private final Status status; + + @Nullable private final Throwable throwable; - private TestExecutionResult(Status status, Throwable throwable) { + private TestExecutionResult(Status status, @Nullable Throwable throwable) { this.status = Preconditions.notNull(status, "Status must not be null"); this.throwable = throwable; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java index 70a04ce893dc..bf3ee3fc6d47 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java @@ -21,6 +21,7 @@ import java.util.Set; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; @@ -84,7 +85,7 @@ public final class TestTag implements Serializable { * @see #RESERVED_CHARACTERS * @see TestTag#create(String) */ - public static boolean isValid(String name) { + public static boolean isValid(@Nullable String name) { if (name == null) { return false; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java index 49d15a8bbb19..ce1bb7654029 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java @@ -23,6 +23,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; @@ -92,6 +93,7 @@ public static UniqueId root(String segmentType, String value) { private transient int hashCode; // lazily computed + @Nullable private transient SoftReference toString; private UniqueId(UniqueIdFormat uniqueIdFormat, Segment segment) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java index 38c412c1e5c1..06e5d605a249 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.ReflectionSupport; @@ -47,12 +48,15 @@ @API(status = STABLE, since = "1.0") public class ClassSelector implements DiscoverySelector { + @Nullable private final ClassLoader classLoader; + private final String className; + @Nullable private Class javaClass; - ClassSelector(ClassLoader classLoader, String className) { + ClassSelector(@Nullable ClassLoader classLoader, String className) { this.className = className; this.classLoader = classLoader; } @@ -70,7 +74,7 @@ public class ClassSelector implements DiscoverySelector { * @since 1.10 */ @API(status = EXPERIMENTAL, since = "1.10") - public ClassLoader getClassLoader() { + public @Nullable ClassLoader getClassLoader() { return this.classLoader; } @@ -94,7 +98,7 @@ public Class getJavaClass() { Try> tryToLoadClass = this.classLoader == null ? ReflectionSupport.tryToLoadClass(this.className) : ReflectionSupport.tryToLoadClass(this.className, this.classLoader); - this.javaClass = tryToLoadClass.getOrThrow(cause -> + this.javaClass = tryToLoadClass.getNonNullOrThrow(cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java index 052462be8e93..774b917d2267 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java @@ -21,6 +21,7 @@ import java.util.Set; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.Resource; @@ -55,10 +56,14 @@ public class ClasspathResourceSelector implements DiscoverySelector { private final String classpathResourceName; + + @Nullable private final FilePosition position; + + @Nullable private Set classpathResources; - ClasspathResourceSelector(String classpathResourceName, FilePosition position) { + ClasspathResourceSelector(String classpathResourceName, @Nullable FilePosition position) { boolean startsWithSlash = classpathResourceName.startsWith("/"); this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); this.position = position; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java index 63aacfb900f0..f844100d44d6 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java @@ -54,8 +54,9 @@ static Optional parse(String identifier) { static Optional parse(DiscoverySelectorIdentifier identifier) { Preconditions.notNull(identifier, "identifier must not be null"); - DiscoverySelectorIdentifierParser parser = Singleton.INSTANCE.parsersByPrefix.get(identifier.getPrefix()); - Preconditions.notNull(parser, "No parser for prefix: " + identifier.getPrefix()); + DiscoverySelectorIdentifierParser parser = Preconditions.notNull( + Singleton.INSTANCE.parsersByPrefix.get(identifier.getPrefix()), + "No parser for prefix: " + identifier.getPrefix()); return parser.parse(identifier, DiscoverySelectorIdentifierParsers::parse); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java index 5b0edccb7c3f..d65b7977ed1e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java @@ -29,6 +29,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.support.Resource; @@ -153,7 +154,7 @@ public static FileSelector selectFile(File file) { * @see #selectDirectory(String) * @see #selectDirectory(File) */ - public static FileSelector selectFile(String path, FilePosition position) { + public static FileSelector selectFile(String path, @Nullable FilePosition position) { Preconditions.notBlank(path, "File path must not be null or blank"); return new FileSelector(path, position); } @@ -174,7 +175,7 @@ public static FileSelector selectFile(String path, FilePosition position) { * @see #selectDirectory(String) * @see #selectDirectory(File) */ - public static FileSelector selectFile(File file, FilePosition position) { + public static FileSelector selectFile(File file, @Nullable FilePosition position) { Preconditions.notNull(file, "File must not be null"); Preconditions.condition(file.isFile(), () -> "The supplied java.io.File [%s] must represent an existing file".formatted(file)); @@ -321,7 +322,7 @@ public static ClasspathResourceSelector selectClasspathResource(String classpath * @see ClassLoader#getResources(String) */ public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, - FilePosition position) { + @Nullable FilePosition position) { Preconditions.notBlank(classpathResourceName, "classpath resource name must not be null or blank"); return new ClasspathResourceSelector(classpathResourceName, position); } @@ -446,7 +447,7 @@ public static ClassSelector selectClass(String className) { * @see ClassSelector */ @API(status = EXPERIMENTAL, since = "1.10") - public static ClassSelector selectClass(ClassLoader classLoader, String className) { + public static ClassSelector selectClass(@Nullable ClassLoader classLoader, String className) { Preconditions.notBlank(className, "Class name must not be null or blank"); return new ClassSelector(classLoader, className); } @@ -514,7 +515,7 @@ public static MethodSelector selectMethod(String fullyQualifiedMethodName) throw * @see MethodSelector */ @API(status = EXPERIMENTAL, since = "1.10") - public static MethodSelector selectMethod(ClassLoader classLoader, String fullyQualifiedMethodName) + public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, String fullyQualifiedMethodName) throws PreconditionViolationException { String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); return selectMethod(classLoader, methodParts[0], methodParts[1], methodParts[2]); @@ -546,7 +547,7 @@ public static MethodSelector selectMethod(String className, String methodName) { * @see MethodSelector */ @API(status = EXPERIMENTAL, since = "1.10") - public static MethodSelector selectMethod(ClassLoader classLoader, String className, String methodName) { + public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, String className, String methodName) { return selectMethod(classLoader, className, methodName, ""); } @@ -590,7 +591,7 @@ public static MethodSelector selectMethod(String className, String methodName, S * @see MethodSelector */ @API(status = EXPERIMENTAL, since = "1.10") - public static MethodSelector selectMethod(ClassLoader classLoader, String className, String methodName, + public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, String className, String methodName, String parameterTypeNames) { Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); @@ -731,8 +732,8 @@ public static NestedClassSelector selectNestedClass(List enclosingClassN * @see NestedClassSelector */ @API(status = EXPERIMENTAL, since = "1.10") - public static NestedClassSelector selectNestedClass(ClassLoader classLoader, List enclosingClassNames, - String nestedClassName) { + public static NestedClassSelector selectNestedClass(@Nullable ClassLoader classLoader, + List enclosingClassNames, String nestedClassName) { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); return new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName); @@ -767,8 +768,9 @@ public static NestedMethodSelector selectNestedMethod(List enclosingClas * @see NestedMethodSelector */ @API(status = EXPERIMENTAL, since = "1.10") - public static NestedMethodSelector selectNestedMethod(ClassLoader classLoader, List enclosingClassNames, - String nestedClassName, String methodName) throws PreconditionViolationException { + public static NestedMethodSelector selectNestedMethod(@Nullable ClassLoader classLoader, + List enclosingClassNames, String nestedClassName, String methodName) + throws PreconditionViolationException { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); @@ -815,8 +817,8 @@ public static NestedMethodSelector selectNestedMethod(List enclosingClas * @see #selectNestedMethod(List, String, String, String) */ @API(status = EXPERIMENTAL, since = "1.10") - public static NestedMethodSelector selectNestedMethod(ClassLoader classLoader, List enclosingClassNames, - String nestedClassName, String methodName, String parameterTypeNames) { + public static NestedMethodSelector selectNestedMethod(@Nullable ClassLoader classLoader, + List enclosingClassNames, String nestedClassName, String methodName, String parameterTypeNames) { Preconditions.notEmpty(enclosingClassNames, "Enclosing class names must not be null or empty"); Preconditions.notBlank(nestedClassName, "Nested class name must not be null or blank"); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java index 0383e03179fb..eed02fb5ecc7 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; @@ -121,6 +122,8 @@ else if (column == null && "column".equals(key)) { } private final int line; + + @Nullable private final Integer column; private FilePosition(int line) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java index cd59c69e7fbf..04f9ffb4c23c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java @@ -21,6 +21,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; @@ -44,9 +45,11 @@ public class FileSelector implements DiscoverySelector { private final String path; + + @Nullable private final FilePosition position; - FileSelector(String path, FilePosition position) { + FileSelector(String path, @Nullable FilePosition position) { this.path = path; this.position = position; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java index 7817c0d1f6fe..41c400874314 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java @@ -19,6 +19,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; @@ -59,19 +60,24 @@ @API(status = STABLE, since = "1.0") public class MethodSelector implements DiscoverySelector { + @Nullable private final ClassLoader classLoader; private final String className; private final String methodName; private final String parameterTypeNames; + @Nullable private volatile Class javaClass; + + @Nullable private volatile Method javaMethod; - private volatile Class[] parameterTypes; + + private volatile Class @Nullable [] parameterTypes; /** * @since 1.10 */ - MethodSelector(ClassLoader classLoader, String className, String methodName, String parameterTypeNames) { + MethodSelector(@Nullable ClassLoader classLoader, String className, String methodName, String parameterTypeNames) { this.classLoader = classLoader; this.className = className; this.methodName = methodName; @@ -89,7 +95,7 @@ public class MethodSelector implements DiscoverySelector { /** * @since 1.10 */ - MethodSelector(ClassLoader classLoader, String className, String methodName, Class... parameterTypes) { + MethodSelector(@Nullable ClassLoader classLoader, String className, String methodName, Class... parameterTypes) { this.classLoader = classLoader; this.className = className; this.methodName = methodName; @@ -126,7 +132,7 @@ public class MethodSelector implements DiscoverySelector { * @since 1.10 */ @API(status = EXPERIMENTAL, since = "1.10") - public ClassLoader getClassLoader() { + public @Nullable ClassLoader getClassLoader() { return this.classLoader; } @@ -176,8 +182,7 @@ public String getParameterTypeNames() { * @see #getJavaMethod() */ public Class getJavaClass() { - lazyLoadJavaClass(); - return this.javaClass; + return lazyLoadJavaClass(); } /** @@ -190,8 +195,7 @@ public Class getJavaClass() { * @see #getJavaClass() */ public Method getJavaMethod() { - lazyLoadJavaMethod(); - return this.javaMethod; + return lazyLoadJavaMethod(); } /** @@ -211,48 +215,54 @@ public Method getJavaMethod() { */ @API(status = EXPERIMENTAL, since = "1.10") public Class[] getParameterTypes() { - lazyLoadParameterTypes(); - return this.parameterTypes.clone(); + return lazyLoadParameterTypes().clone(); } - private void lazyLoadJavaClass() { - // @formatter:off - if (this.javaClass == null) { + private Class lazyLoadJavaClass() { + Class value = this.javaClass; + if (value == null) { + // @formatter:off Try> tryToLoadClass = this.classLoader == null ? ReflectionSupport.tryToLoadClass(this.className) : ReflectionSupport.tryToLoadClass(this.className, this.classLoader); - this.javaClass = tryToLoadClass.getOrThrow(cause -> + value = tryToLoadClass.getNonNullOrThrow(cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); + // @formatter:on + this.javaClass = value; } - // @formatter:on + return value; } - private void lazyLoadJavaMethod() { - if (this.javaMethod == null) { - lazyLoadJavaClass(); - lazyLoadParameterTypes(); - if (this.parameterTypes.length > 0) { - this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName, - this.parameterTypes).orElseThrow( - () -> new PreconditionViolationException( - "Could not find method with name [%s] and parameter types [%s] in class [%s].".formatted( - this.methodName, this.parameterTypeNames, this.javaClass.getName()))); + private Method lazyLoadJavaMethod() { + var value = this.javaMethod; + if (value == null) { + Class javaClass = lazyLoadJavaClass(); + var parameterTypes = lazyLoadParameterTypes(); + if (parameterTypes.length > 0) { + value = ReflectionSupport.findMethod(javaClass, this.methodName, parameterTypes).orElseThrow( + () -> new PreconditionViolationException( + "Could not find method with name [%s] and parameter types [%s] in class [%s].".formatted( + this.methodName, this.parameterTypeNames, javaClass.getName()))); } else { - this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName).orElseThrow( + value = ReflectionSupport.findMethod(javaClass, this.methodName).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] in class [%s].".formatted(this.methodName, - this.javaClass.getName()))); + javaClass.getName()))); } + this.javaMethod = value; } + return value; } - private void lazyLoadParameterTypes() { - if (this.parameterTypes == null) { - lazyLoadJavaClass(); - this.parameterTypes = ReflectionUtils.resolveParameterTypes(this.javaClass, this.methodName, + private Class[] lazyLoadParameterTypes() { + var value = this.parameterTypes; + if (value == null) { + value = ReflectionUtils.resolveParameterTypes(lazyLoadJavaClass(), this.methodName, this.parameterTypeNames); + this.parameterTypes = value; } + return value; } /** diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java index c61f68d6df18..8c2b67753525 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java @@ -24,6 +24,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; @@ -54,11 +55,13 @@ @API(status = STABLE, since = "1.6") public class NestedClassSelector implements DiscoverySelector { + @Nullable private final ClassLoader classLoader; + private final List enclosingClassSelectors; private final ClassSelector nestedClassSelector; - NestedClassSelector(ClassLoader classLoader, List enclosingClassNames, String nestedClassName) { + NestedClassSelector(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName) { this.classLoader = classLoader; this.enclosingClassSelectors = enclosingClassNames.stream() // .map(className -> new ClassSelector(classLoader, className)) // @@ -79,7 +82,7 @@ public class NestedClassSelector implements DiscoverySelector { * @since 1.10 */ @API(status = EXPERIMENTAL, since = "1.10") - public ClassLoader getClassLoader() { + public @Nullable ClassLoader getClassLoader() { return this.classLoader; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java index 7d57ec1751ec..8298167f3fb8 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java @@ -21,6 +21,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ToStringBuilder; @@ -60,7 +61,7 @@ public class NestedMethodSelector implements DiscoverySelector { private final NestedClassSelector nestedClassSelector; private final MethodSelector methodSelector; - NestedMethodSelector(ClassLoader classLoader, List enclosingClassNames, String nestedClassName, + NestedMethodSelector(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName, String methodName, String parameterTypeNames) { this.nestedClassSelector = new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName); this.methodSelector = new MethodSelector(classLoader, nestedClassName, methodName, parameterTypeNames); @@ -69,7 +70,7 @@ public class NestedMethodSelector implements DiscoverySelector { /** * @since 1.10 */ - NestedMethodSelector(ClassLoader classLoader, List enclosingClassNames, String nestedClassName, + NestedMethodSelector(@Nullable ClassLoader classLoader, List enclosingClassNames, String nestedClassName, String methodName, Class... parameterTypes) { this.nestedClassSelector = new NestedClassSelector(classLoader, enclosingClassNames, nestedClassName); this.methodSelector = new MethodSelector(classLoader, nestedClassName, methodName, parameterTypes); @@ -101,7 +102,7 @@ public class NestedMethodSelector implements DiscoverySelector { * @since 1.10 */ @API(status = EXPERIMENTAL, since = "1.10") - public ClassLoader getClassLoader() { + public @Nullable ClassLoader getClassLoader() { return this.nestedClassSelector.getClassLoader(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java index 55321771b8e4..9f8135af9449 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/package-info.java @@ -4,4 +4,7 @@ * {@linkplain org.junit.platform.engine.EngineDiscoveryRequest discovery requests}. */ +@NullMarked package org.junit.platform.engine.discovery; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java index 63bb41729648..02ab768dc6f8 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/package-info.java @@ -2,4 +2,7 @@ * Public API for test engines. */ +@NullMarked package org.junit.platform.engine; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java index 13ad5f022b52..2c74c860c7c3 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java @@ -17,6 +17,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; @@ -38,15 +39,17 @@ public final class FileEntry { * @param mediaType the media type of the path to publish; may be * {@code null} */ - public static FileEntry from(Path path, String mediaType) { + public static FileEntry from(Path path, @Nullable String mediaType) { return new FileEntry(path, mediaType); } private final LocalDateTime timestamp = LocalDateTime.now(); private final Path path; + + @Nullable private final String mediaType; - private FileEntry(Path path, String mediaType) { + private FileEntry(Path path, @Nullable String mediaType) { this.path = Preconditions.notNull(path, "path must not be null"); this.mediaType = mediaType; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java index 11965058d1ab..6cafad5c020e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/package-info.java @@ -3,4 +3,7 @@ * listeners. */ +@NullMarked package org.junit.platform.engine.reporting; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java index 05f555a220ea..76d01748f8e3 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/package-info.java @@ -3,4 +3,7 @@ * classes intended to be used by test engine implementations. */ +@NullMarked package org.junit.platform.engine.support.config; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java index 336b6d686d95..0454b5c34dad 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java @@ -22,6 +22,7 @@ import java.util.function.UnaryOperator; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; @@ -44,8 +45,10 @@ public abstract class AbstractTestDescriptor implements TestDescriptor { private final String displayName; + @Nullable private final TestSource source; + @Nullable private TestDescriptor parent; /** @@ -87,7 +90,7 @@ protected AbstractTestDescriptor(UniqueId uniqueId, String displayName) { * {@code TestDescriptor}; can be {@code null} * @see #AbstractTestDescriptor(UniqueId, String) */ - protected AbstractTestDescriptor(UniqueId uniqueId, String displayName, TestSource source) { + protected AbstractTestDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source) { this.uniqueId = Preconditions.notNull(uniqueId, "UniqueId must not be null"); this.displayName = Preconditions.notBlank(displayName, "displayName must not be null or blank"); this.source = source; @@ -119,7 +122,7 @@ public final Optional getParent() { } @Override - public final void setParent(TestDescriptor parent) { + public final void setParent(@Nullable TestDescriptor parent) { this.parent = parent; } @@ -144,8 +147,8 @@ public void removeChild(TestDescriptor child) { @Override public void removeFromHierarchy() { - Preconditions.condition(!isRoot(), "cannot remove the root of a hierarchy"); - this.parent.removeChild(this); + var parent = Preconditions.notNull(this.parent, "cannot remove the root of a hierarchy"); + parent.removeChild(this); this.children.forEach(child -> child.setParent(null)); this.children.clear(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java index ce76cc6549db..f31e3d948a14 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; @@ -71,7 +72,7 @@ public static ClassSource from(String className) { * @param className the class name; must not be {@code null} or blank * @param filePosition the position in the source file; may be {@code null} */ - public static ClassSource from(String className, FilePosition filePosition) { + public static ClassSource from(String className, @Nullable FilePosition filePosition) { return new ClassSource(className, filePosition); } @@ -132,14 +133,18 @@ public static ClassSource from(URI uri) { } private final String className; + + @Nullable private final FilePosition filePosition; + + @Nullable private Class javaClass; private ClassSource(String className) { this(className, null); } - private ClassSource(String className, FilePosition filePosition) { + private ClassSource(String className, @Nullable FilePosition filePosition) { this.className = Preconditions.notBlank(className, "Class name must not be null or blank"); this.filePosition = filePosition; } @@ -148,7 +153,7 @@ private ClassSource(Class javaClass) { this(javaClass, null); } - private ClassSource(Class javaClass, FilePosition filePosition) { + private ClassSource(Class javaClass, @Nullable FilePosition filePosition) { this.javaClass = Preconditions.notNull(javaClass, "Class must not be null"); this.className = this.javaClass.getName(); this.filePosition = filePosition; @@ -177,7 +182,7 @@ public final String getClassName() { public final Class getJavaClass() { if (this.javaClass == null) { // @formatter:off - this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getOrThrow( + this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getNonNullOrThrow( cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java index 303bdc5bc5c4..3f547a564606 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ResourceUtils; @@ -79,7 +80,7 @@ public static ClasspathResourceSource from(String classpathResourceName) { * {@code null} or blank * @param filePosition the position in the classpath resource; may be {@code null} */ - public static ClasspathResourceSource from(String classpathResourceName, FilePosition filePosition) { + public static ClasspathResourceSource from(String classpathResourceName, @Nullable FilePosition filePosition) { return new ClasspathResourceSource(classpathResourceName, filePosition); } @@ -112,13 +113,15 @@ public static ClasspathResourceSource from(URI uri) { } private final String classpathResourceName; + + @Nullable private final FilePosition filePosition; private ClasspathResourceSource(String classpathResourceName) { this(classpathResourceName, null); } - private ClasspathResourceSource(String classpathResourceName, FilePosition filePosition) { + private ClasspathResourceSource(String classpathResourceName, @Nullable FilePosition filePosition) { Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); boolean startsWithSlash = classpathResourceName.startsWith("/"); this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java index 28d4246a8cdd..de653d5fcacb 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java @@ -18,6 +18,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; @@ -116,6 +117,8 @@ else if (column == null && "column".equals(key)) { } private final int line; + + @Nullable private final Integer column; private FilePosition(int line) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java index b07fddbe7b65..b82e113ff5ee 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java @@ -20,6 +20,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; @@ -53,18 +54,20 @@ public static FileSource from(File file) { * @param file the source file; must not be {@code null} * @param filePosition the position in the source file; may be {@code null} */ - public static FileSource from(File file, FilePosition filePosition) { + public static FileSource from(File file, @Nullable FilePosition filePosition) { return new FileSource(file, filePosition); } private final File file; + + @Nullable private final FilePosition filePosition; private FileSource(File file) { this(file, null); } - private FileSource(File file, FilePosition filePosition) { + private FileSource(File file, @Nullable FilePosition filePosition) { Preconditions.notNull(file, "file must not be null"); try { this.file = file.getCanonicalFile(); @@ -111,7 +114,8 @@ public boolean equals(Object o) { return false; } FileSource that = (FileSource) o; - return Objects.equals(this.file, that.file) && Objects.equals(this.filePosition, that.filePosition); + return Objects.equals(this.file, that.file) // + && Objects.equals(this.filePosition, that.filePosition); } @Override diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java index c00af72f9b45..b3c95915dd52 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java @@ -18,6 +18,7 @@ import java.util.Objects; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; @@ -108,15 +109,21 @@ public static MethodSource from(Class testClass, Method testMethod) { private final String className; private final String methodName; + + @Nullable private final String methodParameterTypes; + + @Nullable private Class javaClass; + + @Nullable private transient Method javaMethod; private MethodSource(String className, String methodName) { this(className, methodName, null); } - private MethodSource(String className, String methodName, String methodParameterTypes) { + private MethodSource(String className, String methodName, @Nullable String methodParameterTypes) { Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); this.className = className; @@ -158,7 +165,7 @@ public final String getMethodName() { /** * Get the method parameter types of this source. */ - public final String getMethodParameterTypes() { + public final @Nullable String getMethodParameterTypes() { return this.methodParameterTypes; } @@ -174,8 +181,7 @@ public final String getMethodParameterTypes() { */ @API(status = STABLE, since = "1.7") public final Class getJavaClass() { - lazyLoadJavaClass(); - return this.javaClass; + return lazyLoadJavaClass(); } /** @@ -190,37 +196,37 @@ public final Class getJavaClass() { */ @API(status = STABLE, since = "1.7") public final Method getJavaMethod() { - lazyLoadJavaMethod(); - return this.javaMethod; + return lazyLoadJavaMethod(); } - private void lazyLoadJavaClass() { + private Class lazyLoadJavaClass() { if (this.javaClass == null) { // @formatter:off - this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getOrThrow( + this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getNonNullOrThrow( cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } + return this.javaClass; } - private void lazyLoadJavaMethod() { - lazyLoadJavaClass(); - + private Method lazyLoadJavaMethod() { if (this.javaMethod == null) { + Class javaClass = getJavaClass(); if (StringUtils.isNotBlank(this.methodParameterTypes)) { - this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName, + this.javaMethod = ReflectionSupport.findMethod(javaClass, this.methodName, this.methodParameterTypes).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] and parameter types [%s] in class [%s].".formatted( - this.methodName, this.methodParameterTypes, this.javaClass.getName()))); + this.methodName, this.methodParameterTypes, javaClass.getName()))); } else { - this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName).orElseThrow( + this.javaMethod = ReflectionSupport.findMethod(javaClass, this.methodName).orElseThrow( () -> new PreconditionViolationException( "Could not find method with name [%s] in class [%s].".formatted(this.methodName, - this.javaClass.getName()))); + javaClass.getName()))); } } + return this.javaMethod; } @Override diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java index 9f063532a19a..eabc3dab0055 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/package-info.java @@ -4,4 +4,7 @@ * the launcher. */ +@NullMarked package org.junit.platform.engine.support.descriptor; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java index 2c4590fd11be..1726376ffa67 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java @@ -10,6 +10,7 @@ package org.junit.platform.engine.support.discovery; +import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.joining; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.failed; @@ -28,6 +29,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.DiscoverySelector; @@ -199,18 +201,16 @@ private Optional resolve(DiscoverySelector selector, private class DefaultContext implements Context { + @Nullable private final TestDescriptor parent; - DefaultContext(TestDescriptor parent) { + DefaultContext(@Nullable TestDescriptor parent) { this.parent = parent; } @Override public Optional addToParent(Function> creator) { - if (parent != null) { - return createAndAdd(parent, creator); - } - return createAndAdd(engineDescriptor, creator); + return createAndAdd(requireNonNullElse(parent, engineDescriptor), creator); } @Override diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java index 1f0ea1ff308a..3d6b8dd4a0aa 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/package-info.java @@ -6,4 +6,7 @@ * @see org.junit.platform.engine.support.discovery.SelectorResolver */ +@NullMarked package org.junit.platform.engine.support.discovery; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index 7bf7c9581f95..91bc18a23589 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -10,6 +10,7 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; @@ -106,8 +107,8 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter + ParallelExecutionConfigurationStrategy.class); return (ParallelExecutionConfigurationStrategy) ReflectionSupport.newInstance(strategyClass); }) // - .andThenTry(strategy -> strategy.createConfiguration(configurationParameters)) // - .getOrThrow(cause -> new JUnitException( + .andThenTry(strategy -> requireNonNull(strategy).createConfiguration(configurationParameters)) // + .getNonNullOrThrow(cause -> new JUnitException( "Could not create configuration for strategy class: " + className, cause)); } }; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java index 9ccb3747680f..5d6979cf8b32 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java @@ -35,6 +35,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.LoggerFactory; @@ -126,7 +127,7 @@ private static Callable sinceJava7ConstructorInvocation(ParallelEx } @Override - public Future submit(TestTask testTask) { + public Future<@Nullable Void> submit(TestTask testTask) { ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); if (!isAlreadyRunningInForkJoinPool()) { // ensure we're running inside the ForkJoinPool so we diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java index d879aa2b5978..1fed0663e519 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java @@ -16,6 +16,7 @@ import java.util.concurrent.Future; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; @@ -49,7 +50,7 @@ public interface HierarchicalTestExecutorService extends AutoCloseable { * to be finished * @see #invokeAll(List) */ - Future submit(TestTask testTask); + Future<@Nullable Void> submit(TestTask testTask); /** * Invoke all supplied {@linkplain TestTask test tasks} and block until diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java index f2616e60a6cb..1fa014c92d14 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java @@ -19,6 +19,7 @@ import java.util.concurrent.Future; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; @@ -218,7 +219,7 @@ class SkipResult { * may be {@code null} * @return a skipped {@code SkipResult} with the given reason */ - public static SkipResult skip(String reason) { + public static SkipResult skip(@Nullable String reason) { return new SkipResult(true, reason); } @@ -233,7 +234,7 @@ public static SkipResult doNotSkip() { return alwaysExecuteSkipResult; } - private SkipResult(boolean skipped, String reason) { + private SkipResult(boolean skipped, @Nullable String reason) { this.skipped = skipped; this.reason = Optional.ofNullable(reason); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java index 4bdbcbbcdd26..5ad5e15a37a4 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java @@ -10,6 +10,8 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.Objects.requireNonNull; +import static java.util.Objects.requireNonNullElse; import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.stream.Collectors.toCollection; import static org.junit.platform.engine.TestExecutionResult.failed; @@ -23,6 +25,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; @@ -51,11 +54,18 @@ class NodeTestTask implements TestTask { private final Node node; private final Runnable finalizer; + @Nullable private C parentContext; + + @Nullable private C context; + @Nullable private SkipResult skipResult; + private boolean started; + + @Nullable private ThrowableCollector throwableCollector; NodeTestTask(NodeTestTaskContext taskContext, TestDescriptor testDescriptor) { @@ -85,7 +95,7 @@ public String toString() { return "NodeTestTask [" + testDescriptor + "]"; } - void setParentContext(C parentContext) { + void setParentContext(@Nullable C parentContext) { this.parentContext = parentContext; } @@ -97,7 +107,7 @@ public void execute() { if (throwableCollector.isEmpty()) { checkWhetherSkipped(); } - if (throwableCollector.isEmpty() && !skipResult.isSkipped()) { + if (throwableCollector.isEmpty() && !requiredSkipResult().isSkipped()) { executeRecursively(); } if (context != null) { @@ -126,7 +136,7 @@ public void execute() { } private void prepare() { - throwableCollector.execute(() -> context = node.prepare(parentContext)); + requiredThrowableCollector().execute(() -> context = node.prepare(requireNonNull(parentContext))); // Clear reference to parent context to allow it to be garbage collected. // See https://github.com/junit-team/junit5/issues/1578 @@ -134,15 +144,17 @@ private void prepare() { } private void checkWhetherSkipped() { - throwableCollector.execute(() -> skipResult = node.shouldBeSkipped(context)); + requiredThrowableCollector().execute(() -> skipResult = node.shouldBeSkipped(requiredContext())); } private void executeRecursively() { taskContext.getListener().executionStarted(testDescriptor); started = true; + var throwableCollector = requiredThrowableCollector(); + throwableCollector.execute(() -> { - node.around(context, ctx -> { + node.around(requiredContext(), ctx -> { context = ctx; throwableCollector.execute(() -> { // @formatter:off @@ -151,10 +163,10 @@ private void executeRecursively() { .collect(toCollection(ArrayList::new)); // @formatter:on - context = node.before(context); + context = node.before(requiredContext()); final DynamicTestExecutor dynamicTestExecutor = new DefaultDynamicTestExecutor(); - context = node.execute(context, dynamicTestExecutor); + context = node.execute(requiredContext(), dynamicTestExecutor); if (!children.isEmpty()) { children.forEach(child -> child.setParentContext(context)); @@ -164,19 +176,23 @@ private void executeRecursively() { throwableCollector.execute(dynamicTestExecutor::awaitFinished); }); - throwableCollector.execute(() -> node.after(context)); + throwableCollector.execute(() -> node.after(requiredContext())); }); }); } private void cleanUp() { - throwableCollector.execute(() -> node.cleanUp(context)); + requiredThrowableCollector().execute(() -> node.cleanUp(requiredContext())); } private void reportCompletion() { - if (throwableCollector.isEmpty() && skipResult.isSkipped()) { + + var throwableCollector = requiredThrowableCollector(); + + if (throwableCollector.isEmpty() && requiredSkipResult().isSkipped()) { + var skipResult = requiredSkipResult(); try { - node.nodeSkipped(context, testDescriptor, skipResult); + node.nodeSkipped(requiredContext(), testDescriptor, skipResult); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); @@ -191,7 +207,7 @@ private void reportCompletion() { taskContext.getListener().executionStarted(testDescriptor); } try { - node.nodeFinished(context, testDescriptor, throwableCollector.toTestExecutionResult()); + node.nodeFinished(requiredContext(), testDescriptor, throwableCollector.toTestExecutionResult()); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); @@ -199,7 +215,19 @@ private void reportCompletion() { () -> "Failed to invoke nodeFinished() on Node %s".formatted(testDescriptor.getUniqueId())); } taskContext.getListener().executionFinished(testDescriptor, throwableCollector.toTestExecutionResult()); - throwableCollector = null; + this.throwableCollector = null; + } + + private C requiredContext() { + return requireNonNull(context); + } + + private SkipResult requiredSkipResult() { + return requireNonNull(skipResult); + } + + private ThrowableCollector requiredThrowableCollector() { + return requireNonNull(throwableCollector); } private class DefaultDynamicTestExecutor implements DynamicTestExecutor { @@ -245,7 +273,7 @@ public void awaitFinished() throws InterruptedException { // Futures returned by execute() may have been cancelled } catch (ExecutionException e) { - throw ExceptionUtils.throwAsUncheckedException(e.getCause()); + throw ExceptionUtils.throwAsUncheckedException(requireNonNullElse(e.getCause(), e)); } } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java index 15f9a3184e7e..b7cd9cb64ffe 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java @@ -16,6 +16,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Configuration to use for parallel test execution. @@ -66,10 +67,10 @@ public interface ParallelExecutionConfiguration { * @return the saturate predicate to be passed to the {@code ForkJoinPool} constructor; may be {@code null} * @since 1.9 * @see ForkJoinPool#ForkJoinPool(int, ForkJoinPool.ForkJoinWorkerThreadFactory, Thread.UncaughtExceptionHandler, - * boolean, int, int, int, Predicate, long, TimeUnit) + * boolean, int, int, int, Predicate, long, java.util.concurrent.TimeUnit) */ @API(status = STABLE, since = "1.11") - default Predicate getSaturatePredicate() { + default @Nullable Predicate getSaturatePredicate() { return null; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java index d6293ed7874b..9255e9df16e7 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java @@ -17,6 +17,7 @@ import java.util.concurrent.Future; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * A simple {@linkplain HierarchicalTestExecutorService executor service} that @@ -31,7 +32,7 @@ public SameThreadHierarchicalTestExecutorService() { } @Override - public Future submit(TestTask testTask) { + public Future<@Nullable Void> submit(TestTask testTask) { testTask.execute(); return completedFuture(null); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java index 88e83cdb9ed5..f322178a62fa 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java @@ -18,6 +18,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; @@ -42,6 +43,7 @@ public class ThrowableCollector { private final Predicate abortedExecutionPredicate; + @Nullable private Throwable throwable; /** @@ -121,7 +123,7 @@ else if (throwable != t) { * @see #isEmpty() * @see #assertEmpty() */ - public Throwable getThrowable() { + public @Nullable Throwable getThrowable() { return this.throwable; } @@ -157,7 +159,7 @@ public boolean isNotEmpty() { * @see ExceptionUtils#throwAsUncheckedException(Throwable) */ public void assertEmpty() { - if (!isEmpty()) { + if (this.throwable != null) { throw ExceptionUtils.throwAsUncheckedException(this.throwable); } } @@ -174,13 +176,13 @@ public void assertEmpty() { */ @API(status = MAINTAINED, since = "1.6") public TestExecutionResult toTestExecutionResult() { - if (isEmpty()) { + if (this.throwable == null) { return successful(); } - if (hasAbortedExecution(throwable)) { - return aborted(throwable); + if (hasAbortedExecution(this.throwable)) { + return aborted(this.throwable); } - return failed(throwable); + return failed(this.throwable); } private boolean hasAbortedExecution(Throwable t) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java index 033745ba7795..925cfac6bfec 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/package-info.java @@ -5,4 +5,7 @@ * {@link org.junit.platform.engine.support.hierarchical.Node} abstraction. */ +@NullMarked package org.junit.platform.engine.support.hierarchical; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java index 916a87bcd97e..3f3c46278352 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java @@ -11,6 +11,7 @@ package org.junit.platform.engine.support.store; import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; import static org.junit.platform.commons.util.ReflectionUtils.isAssignableTo; @@ -25,6 +26,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; @@ -51,8 +53,10 @@ public final class NamespacedHierarchicalStore implements AutoCloseable { private final ConcurrentMap, StoredValue> storedValues = new ConcurrentHashMap<>(4); + @Nullable private final NamespacedHierarchicalStore parentStore; + @Nullable private final CloseAction closeAction; private volatile boolean closed = false; @@ -62,7 +66,7 @@ public final class NamespacedHierarchicalStore implements AutoCloseable { * * @param parentStore the parent store to use for lookups; may be {@code null} */ - public NamespacedHierarchicalStore(NamespacedHierarchicalStore parentStore) { + public NamespacedHierarchicalStore(@Nullable NamespacedHierarchicalStore parentStore) { this(parentStore, null); } @@ -73,7 +77,8 @@ public NamespacedHierarchicalStore(NamespacedHierarchicalStore parentStore) { * @param closeAction the action to be called for each stored value when this * store is closed; may be {@code null} */ - public NamespacedHierarchicalStore(NamespacedHierarchicalStore parentStore, CloseAction closeAction) { + public NamespacedHierarchicalStore(@Nullable NamespacedHierarchicalStore parentStore, + @Nullable CloseAction closeAction) { this.parentStore = parentStore; this.closeAction = closeAction; } @@ -152,7 +157,7 @@ public void close() { * @throws NamespacedHierarchicalStoreException if this store has already been * closed */ - public Object get(N namespace, Object key) { + public @Nullable Object get(N namespace, Object key) { StoredValue storedValue = getStoredValue(new CompositeKey<>(namespace, key)); return StoredValue.evaluateIfNotNull(storedValue); } @@ -168,7 +173,8 @@ public Object get(N namespace, Object key) { * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed */ - public T get(N namespace, Object key, Class requiredType) throws NamespacedHierarchicalStoreException { + public @Nullable T get(N namespace, Object key, Class requiredType) + throws NamespacedHierarchicalStoreException { Object value = get(namespace, key); return castToRequiredType(key, value, requiredType); } @@ -185,13 +191,13 @@ public T get(N namespace, Object key, Class requiredType) throws Namespac * @throws NamespacedHierarchicalStoreException if this store has already been * closed */ - public Object getOrComputeIfAbsent(N namespace, K key, Function defaultCreator) { + public @Nullable Object getOrComputeIfAbsent(N namespace, K key, Function defaultCreator) { Preconditions.notNull(defaultCreator, "defaultCreator must not be null"); CompositeKey compositeKey = new CompositeKey<>(namespace, key); StoredValue storedValue = getStoredValue(compositeKey); if (storedValue == null) { storedValue = this.storedValues.computeIfAbsent(compositeKey, - __ -> storedValue(new MemoizingSupplier(() -> { + __ -> newStoredValue(new MemoizingSupplier(() -> { rejectIfClosed(); return defaultCreator.apply(key); }))); @@ -213,8 +219,8 @@ public Object getOrComputeIfAbsent(N namespace, K key, Function def * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed */ - public V getOrComputeIfAbsent(N namespace, K key, Function defaultCreator, Class requiredType) - throws NamespacedHierarchicalStoreException { + public @Nullable V getOrComputeIfAbsent(N namespace, K key, Function defaultCreator, + Class requiredType) throws NamespacedHierarchicalStoreException { Object value = getOrComputeIfAbsent(namespace, key, defaultCreator); return castToRequiredType(key, value, requiredType); @@ -234,9 +240,10 @@ public V getOrComputeIfAbsent(N namespace, K key, Function defaultC * @throws NamespacedHierarchicalStoreException if an error occurs while * storing the value, or if this store has already been closed */ - public Object put(N namespace, Object key, Object value) throws NamespacedHierarchicalStoreException { + public @Nullable Object put(N namespace, Object key, @Nullable Object value) + throws NamespacedHierarchicalStoreException { rejectIfClosed(); - StoredValue oldValue = this.storedValues.put(new CompositeKey<>(namespace, key), storedValue(() -> value)); + StoredValue oldValue = this.storedValues.put(new CompositeKey<>(namespace, key), newStoredValue(() -> value)); return StoredValue.evaluateIfNotNull(oldValue); } @@ -253,7 +260,7 @@ public Object put(N namespace, Object key, Object value) throws NamespacedHierar * @throws NamespacedHierarchicalStoreException if this store has already been * closed */ - public Object remove(N namespace, Object key) { + public @Nullable Object remove(N namespace, Object key) { rejectIfClosed(); StoredValue previous = this.storedValues.remove(new CompositeKey<>(namespace, key)); return StoredValue.evaluateIfNotNull(previous); @@ -273,17 +280,18 @@ public Object remove(N namespace, Object key) { * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed */ - public T remove(N namespace, Object key, Class requiredType) throws NamespacedHierarchicalStoreException { + public @Nullable T remove(N namespace, Object key, Class requiredType) + throws NamespacedHierarchicalStoreException { rejectIfClosed(); Object value = remove(namespace, key); return castToRequiredType(key, value, requiredType); } - private StoredValue storedValue(Supplier value) { + private StoredValue newStoredValue(Supplier<@Nullable Object> value) { return new StoredValue(this.insertOrderSequence.getAndIncrement(), value); } - private StoredValue getStoredValue(CompositeKey compositeKey) { + private @Nullable StoredValue getStoredValue(CompositeKey compositeKey) { StoredValue storedValue = this.storedValues.get(compositeKey); if (storedValue != null) { return storedValue; @@ -295,14 +303,14 @@ private StoredValue getStoredValue(CompositeKey compositeKey) { } @SuppressWarnings("unchecked") - private T castToRequiredType(Object key, Object value, Class requiredType) { + private @Nullable T castToRequiredType(Object key, @Nullable Object value, Class requiredType) { Preconditions.notNull(requiredType, "requiredType must not be null"); if (value == null) { return null; } if (isAssignableTo(value, requiredType)) { if (requiredType.isPrimitive()) { - return (T) getWrapperType(requiredType).cast(value); + return (T) requireNonNull(getWrapperType(requiredType)).cast(value); } return requiredType.cast(value); } @@ -351,14 +359,14 @@ public int hashCode() { private static class StoredValue { private final int order; - private final Supplier supplier; + private final Supplier<@Nullable Object> supplier; - StoredValue(int order, Supplier supplier) { + StoredValue(int order, Supplier<@Nullable Object> supplier) { this.order = order; this.supplier = supplier; } - private EvaluatedValue evaluateSafely(CompositeKey compositeKey) { + private @Nullable EvaluatedValue evaluateSafely(CompositeKey compositeKey) { try { return new EvaluatedValue<>(compositeKey, this.order, evaluate()); } @@ -368,11 +376,11 @@ private EvaluatedValue evaluateSafely(CompositeKey compositeKey) { } } - private Object evaluate() { + private @Nullable Object evaluate() { return this.supplier.get(); } - static Object evaluateIfNotNull(StoredValue value) { + static @Nullable Object evaluateIfNotNull(@Nullable StoredValue value) { return value != null ? value.evaluate() : null; } @@ -385,16 +393,20 @@ private static class EvaluatedValue { private final CompositeKey compositeKey; private final int order; + + @Nullable private final Object value; - private EvaluatedValue(CompositeKey compositeKey, int order, Object value) { + private EvaluatedValue(CompositeKey compositeKey, int order, @Nullable Object value) { this.compositeKey = compositeKey; this.order = order; this.value = value; } private void close(CloseAction closeAction) throws Throwable { - closeAction.close(this.compositeKey.namespace, this.compositeKey.key, this.value); + if (this.value != null) { + closeAction.close(this.compositeKey.namespace, this.compositeKey.key, this.value); + } } } @@ -408,19 +420,21 @@ private void close(CloseAction closeAction) throws Throwable { * * @see StoredValue */ - private static class MemoizingSupplier implements Supplier { + private static class MemoizingSupplier implements Supplier<@Nullable Object> { private static final Object NO_VALUE_SET = new Object(); - private final Supplier delegate; + private final Supplier<@Nullable Object> delegate; + + @Nullable private volatile Object value = NO_VALUE_SET; - private MemoizingSupplier(Supplier delegate) { + private MemoizingSupplier(Supplier<@Nullable Object> delegate) { this.delegate = delegate; } @Override - public Object get() { + public @Nullable Object get() { if (this.value == NO_VALUE_SET) { computeValue(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/package-info.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/package-info.java index d40e1e88e974..e754a7b508de 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/package-info.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/package-info.java @@ -2,4 +2,7 @@ * Reusable data structures for test engines and their extensions. */ +@NullMarked package org.junit.platform.engine.support.store; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java index 00ead8e16784..ea0e595862c7 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java @@ -12,6 +12,7 @@ import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; @@ -22,10 +23,11 @@ public class DemoHierarchicalContainerDescriptor extends AbstractTestDescriptor implements Node { + @Nullable private final Runnable beforeBlock; - public DemoHierarchicalContainerDescriptor(UniqueId uniqueId, String displayName, TestSource source, - Runnable beforeBlock) { + public DemoHierarchicalContainerDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source, + @Nullable Runnable beforeBlock) { super(uniqueId, displayName, source); this.beforeBlock = beforeBlock; } diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java index 5dd40c886bbb..49aa67dd32f8 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java @@ -13,6 +13,7 @@ import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.doNotSkip; import static org.junit.platform.engine.support.hierarchical.Node.SkipResult.skip; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.EngineDescriptor; @@ -21,7 +22,9 @@ */ public class DemoHierarchicalEngineDescriptor extends EngineDescriptor implements Node { + @Nullable private String skippedReason; + private boolean skipped; private Runnable beforeAllBehavior = () -> { }; diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java index 29b57733512a..26b696be0387 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java @@ -15,6 +15,7 @@ import java.util.function.BiConsumer; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; @@ -25,17 +26,21 @@ */ public class DemoHierarchicalTestDescriptor extends AbstractTestDescriptor implements Node { + @Nullable private final BiConsumer executeBlock; + + @Nullable private String skippedReason; + private boolean skipped; public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, - BiConsumer executeBlock) { + @Nullable BiConsumer executeBlock) { this(uniqueId, displayName, null, executeBlock); } - public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, TestSource source, - BiConsumer executeBlock) { + public DemoHierarchicalTestDescriptor(UniqueId uniqueId, String displayName, @Nullable TestSource source, + @Nullable BiConsumer executeBlock) { super(uniqueId, displayName, source); this.executeBlock = executeBlock; } diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java index ff5e32299dde..e381e3504380 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java @@ -13,6 +13,7 @@ import java.util.function.BiConsumer; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -61,7 +62,8 @@ public DemoHierarchicalTestDescriptor addTest(String uniqueName, String displayN "test"); } - public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, TestSource source) { + public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, + @Nullable TestSource source) { return addContainer(uniqueName, displayName, source, null); } @@ -69,8 +71,8 @@ public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, Runna return addContainer(uniqueName, uniqueName, null, beforeBlock); } - public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, TestSource source, - Runnable beforeBlock) { + public DemoHierarchicalContainerDescriptor addContainer(String uniqueName, String displayName, + @Nullable TestSource source, @Nullable Runnable beforeBlock) { return addChild(uniqueName, uniqueId -> new DemoHierarchicalContainerDescriptor(uniqueId, displayName, source, beforeBlock), diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java index 7754c75f411b..040fe144e446 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java @@ -10,6 +10,7 @@ package org.junit.platform.fakes; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -23,6 +24,7 @@ public class TestEngineSpy implements TestEngine { private final String id; + @Nullable public ExecutionRequest requestForExecution; public TestEngineSpy() { diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/package-info.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/package-info.java new file mode 100644 index 000000000000..fae5df1a599a --- /dev/null +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/package-info.java @@ -0,0 +1,5 @@ + +@NullMarked +package org.junit.platform.fakes; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-jfr/junit-platform-jfr.gradle.kts b/junit-platform-jfr/junit-platform-jfr.gradle.kts index 7d9e2d06a2a9..5608f5280e9e 100644 --- a/junit-platform-jfr/junit-platform-jfr.gradle.kts +++ b/junit-platform-jfr/junit-platform-jfr.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") } description = "JUnit Platform Flight Recorder Support" @@ -9,6 +10,7 @@ dependencies { api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) if (java.toolchain.implementation.orNull == JvmImplementation.J9) { compileOnly(libs.jfrPolyfill) { diff --git a/junit-platform-jfr/src/main/java/module-info.java b/junit-platform-jfr/src/main/java/module-info.java index 6f38bf063cf7..3125840a2da4 100644 --- a/junit-platform-jfr/src/main/java/module-info.java +++ b/junit-platform-jfr/src/main/java/module-info.java @@ -20,8 +20,11 @@ * @since 1.7 */ module org.junit.platform.jfr { - requires jdk.jfr; + requires static org.apiguardian.api; + requires static org.jspecify; + + requires jdk.jfr; requires org.junit.platform.engine; requires org.junit.platform.launcher; diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java index ed34f1289360..b0cef89f0f7f 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java @@ -10,6 +10,7 @@ package org.junit.platform.jfr; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.STABLE; import java.util.Map; @@ -23,6 +24,7 @@ import jdk.jfr.StackTrace; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoveryIssue; @@ -41,7 +43,7 @@ @API(status = STABLE, since = "1.11") public class FlightRecordingDiscoveryListener implements LauncherDiscoveryListener { - private final AtomicReference launcherDiscoveryEvent = new AtomicReference<>(); + private final AtomicReference<@Nullable LauncherDiscoveryEvent> launcherDiscoveryEvent = new AtomicReference<>(); private final Map engineDiscoveryEvents = new ConcurrentHashMap<>(); @Override @@ -55,7 +57,7 @@ public void launcherDiscoveryStarted(LauncherDiscoveryRequest request) { @Override public void launcherDiscoveryFinished(LauncherDiscoveryRequest request) { - launcherDiscoveryEvent.getAndSet(null).commit(); + requireNonNull(launcherDiscoveryEvent.getAndSet(null)).commit(); } @Override @@ -106,9 +108,11 @@ static class EngineDiscoveryEvent extends DiscoveryEvent { @UniqueId @Label("Unique Id") + @Nullable String uniqueId; @Label("Result") + @Nullable String result; } @@ -117,18 +121,23 @@ static class EngineDiscoveryEvent extends DiscoveryEvent { static class DiscoveryIssueEvent extends DiscoveryEvent { @Label("Engine Id") + @Nullable String engineId; @Label("Severity") + @Nullable String severity; @Label("Message") + @Nullable String message; @Label("Source") + @Nullable String source; @Label("Cause") + @Nullable String cause; } } diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java index a854b81d98ec..6a2031770605 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java @@ -10,6 +10,7 @@ package org.junit.platform.jfr; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.STABLE; import java.util.Map; @@ -25,6 +26,7 @@ import jdk.jfr.StackTrace; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; @@ -42,7 +44,7 @@ @API(status = STABLE, since = "1.11") public class FlightRecordingExecutionListener implements TestExecutionListener { - private final AtomicReference testPlanExecutionEvent = new AtomicReference<>(); + private final AtomicReference<@Nullable TestPlanExecutionEvent> testPlanExecutionEvent = new AtomicReference<>(); private final Map testExecutionEvents = new ConcurrentHashMap<>(); @Override @@ -57,7 +59,7 @@ public void testPlanExecutionStarted(TestPlan plan) { @Override public void testPlanExecutionFinished(TestPlan plan) { - testPlanExecutionEvent.getAndSet(null).commit(); + requireNonNull(testPlanExecutionEvent.getAndSet(null)).commit(); } @Override @@ -114,21 +116,32 @@ abstract static class ExecutionEvent extends Event { @Label("Test Execution") @Name("org.junit.TestPlanExecution") static class TestPlanExecutionEvent extends ExecutionEvent { + @Label("Contains Tests") boolean containsTests; + @Label("Engine Names") + @Nullable String engineNames; } abstract static class TestEvent extends ExecutionEvent { + @UniqueId @Label("Unique Id") + @Nullable String uniqueId; + @Label("Display Name") + @Nullable String displayName; + @Label("Tags") + @Nullable String tags; + @Label("Type") + @Nullable String type; void initialize(TestIdentifier test) { @@ -143,39 +156,56 @@ void initialize(TestIdentifier test) { @Name("org.junit.SkippedTest") static class SkippedTestEvent extends TestEvent { @Label("Reason") + @Nullable String reason; } @Label("Test") @Name("org.junit.TestExecution") static class TestExecutionEvent extends TestEvent { + @Label("Result") + @Nullable String result; + @Label("Exception Class") + @Nullable Class exceptionClass; + @Label("Exception Message") + @Nullable String exceptionMessage; } @Label("Report Entry") @Name("org.junit.ReportEntry") static class ReportEntryEvent extends ExecutionEvent { + @UniqueId @Label("Unique Id") + @Nullable String uniqueId; + @Label("Key") + @Nullable String key; + @Label("Value") + @Nullable String value; } @Label("File Entry") @Name("org.junit.FileEntry") static class FileEntryEvent extends ExecutionEvent { + @UniqueId @Label("Unique Id") + @Nullable String uniqueId; + @Label("Path") + @Nullable String path; } } diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java index 0a8c981374f7..3da51bbd60f8 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/package-info.java @@ -2,4 +2,7 @@ * Java Flight Recorder support package. */ +@NullMarked package org.junit.platform.jfr; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts index a9b3630762c8..dd6a9ff2a991 100644 --- a/junit-platform-launcher/junit-platform-launcher.gradle.kts +++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") `java-test-fixtures` } @@ -10,6 +11,7 @@ dependencies { api(projects.junitPlatformEngine) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) osgiVerification(projects.junitJupiterEngine) } diff --git a/junit-platform-launcher/src/main/java/module-info.java b/junit-platform-launcher/src/main/java/module-info.java index e4e492183f5a..4aa548b40565 100644 --- a/junit-platform-launcher/src/main/java/module-info.java +++ b/junit-platform-launcher/src/main/java/module-info.java @@ -22,8 +22,11 @@ * @uses org.junit.platform.launcher.TestExecutionListener */ module org.junit.platform.launcher { - requires transitive java.logging; + requires static transitive org.apiguardian.api; + requires static org.jspecify; + + requires transitive java.logging; requires transitive org.junit.platform.commons; requires transitive org.junit.platform.engine; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java index 9ac17deea90c..6c2eb66988da 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestDescriptor; @@ -42,14 +43,14 @@ abstract class AbstractMethodFilter implements MethodFilter { this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'")); } - protected Optional findMatchingPattern(String methodName) { + protected Optional findMatchingPattern(@Nullable String methodName) { if (methodName == null) { return Optional.empty(); } return this.patterns.stream().filter(pattern -> pattern.matcher(methodName).matches()).findAny(); } - protected String getFullyQualifiedMethodNameFromDescriptor(TestDescriptor descriptor) { + protected @Nullable String getFullyQualifiedMethodNameFromDescriptor(TestDescriptor descriptor) { return descriptor.getSource() // .filter(source -> source instanceof MethodSource) // .map(methodSource -> getFullyQualifiedMethodNameWithoutParameters(((MethodSource) methodSource))) // diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java index b3c104b57fa2..c266f3319ab0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ToStringBuilder; /** @@ -66,14 +67,16 @@ public static EngineDiscoveryResult successful() { * {@code null} * @return the {@code EngineDiscoveryResult}; never {@code null} */ - public static EngineDiscoveryResult failed(Throwable throwable) { + public static EngineDiscoveryResult failed(@Nullable Throwable throwable) { return new EngineDiscoveryResult(Status.FAILED, throwable); } private final Status status; + + @Nullable private final Throwable throwable; - private EngineDiscoveryResult(Status status, Throwable throwable) { + private EngineDiscoveryResult(Status status, @Nullable Throwable throwable) { this.status = status; this.throwable = throwable; } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java index 4b17bfb54453..0466277e0894 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java @@ -15,6 +15,7 @@ import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; @@ -41,11 +42,11 @@ public FilterResult apply(TestDescriptor descriptor) { .orElseGet(() -> included(formatInclusionReason(methodName))); } - private String formatInclusionReason(String methodName) { + private String formatInclusionReason(@Nullable String methodName) { return "Method name [%s] does not match any excluded pattern: %s".formatted(methodName, patternDescription); } - private String formatExclusionReason(String methodName, Pattern pattern) { + private String formatExclusionReason(@Nullable String methodName, Pattern pattern) { return "Method name [%s] matches excluded pattern: '%s'".formatted(methodName, pattern); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java index 26a467f9552c..ff0c979f53a4 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java @@ -15,6 +15,7 @@ import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; @@ -41,11 +42,11 @@ public FilterResult apply(TestDescriptor descriptor) { .orElseGet(() -> excluded(formatExclusionReason(methodName))); } - private String formatInclusionReason(String methodName, Pattern pattern) { + private String formatInclusionReason(@Nullable String methodName, Pattern pattern) { return "Method name [%s] matches included pattern: '%s'".formatted(methodName, pattern); } - private String formatExclusionReason(String methodName) { + private String formatExclusionReason(@Nullable String methodName) { return "Method name [%s] does not match any included pattern: %s".formatted(methodName, patternDescription); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java index 490ac65483a2..04dd3edb4c69 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; /** * Interceptor for test discovery and execution by a {@link Launcher} in the @@ -63,7 +64,7 @@ public interface LauncherInterceptor { * @param invocation the intercepted invocation; never {@code null} * @return the result of the invocation */ - T intercept(Invocation invocation); + T intercept(Invocation invocation); /** * Closes this interceptor. @@ -78,7 +79,7 @@ public interface LauncherInterceptor { * *

This interface is not intended to be implemented by clients. */ - interface Invocation { + interface Invocation { T proceed(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java index 406b28d3d613..51e37f95dbc6 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java @@ -20,6 +20,7 @@ import java.util.stream.Collectors; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.FilterResult; @@ -175,7 +176,7 @@ private static List parseAll(List tagExpressions) { return tagExpressions.stream().map(TagFilter::parse).collect(toUnmodifiableList()); } - private static TagExpression parse(String tagExpression) { + private static TagExpression parse(@Nullable String tagExpression) { return TagExpression.parseFrom(tagExpression).tagExpressionOrThrow( message -> new PreconditionViolationException( "Unable to parse tag expression \"" + tagExpression + "\": " + message)); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java index 1d4eca7a17e0..bd4329d26afc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java @@ -30,6 +30,7 @@ import java.util.Set; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; @@ -56,9 +57,14 @@ public final class TestIdentifier implements Serializable { // These are effectively final but not technically due to late initialization when deserializing private /* final */ UniqueId uniqueId; + + @Nullable private /* final */ UniqueId parentId; + private /* final */ String displayName; private /* final */ String legacyReportingName; + + @Nullable private /* final */ TestSource source; private /* final */ Set tags; private /* final */ Type type; @@ -79,8 +85,8 @@ public static TestIdentifier from(TestDescriptor testDescriptor) { return new TestIdentifier(uniqueId, displayName, source, tags, type, parentId, legacyReportingName); } - private TestIdentifier(UniqueId uniqueId, String displayName, TestSource source, Set tags, Type type, - UniqueId parentId, String legacyReportingName) { + private TestIdentifier(UniqueId uniqueId, String displayName, @Nullable TestSource source, Set tags, + Type type, @Nullable UniqueId parentId, String legacyReportingName) { Preconditions.notNull(type, "TestDescriptor.Type must not be null"); this.uniqueId = uniqueId; this.parentId = parentId; @@ -303,10 +309,16 @@ private static class SerializedForm implements Serializable { private static final long serialVersionUID = 1L; private final String uniqueId; + + @Nullable private final String parentId; + private final String displayName; private final String legacyReportingName; + + @Nullable private final TestSource source; + @SuppressWarnings({ "serial", "RedundantSuppression" }) // always used with serializable implementation (see TestIdentifier#copyOf()) private final Set tags; private final Type type; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java index 09a436d7507c..08d811926ec3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java @@ -22,6 +22,7 @@ import java.util.Optional; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; @@ -60,7 +61,7 @@ static Optional check(LinkageError error) { } // VisibleForTesting - static Optional check(LinkageError error, Function packageLookup) { + static Optional check(LinkageError error, Function packageLookup) { Map> packagesByVersions = new HashMap<>(); WELL_KNOWN_PACKAGES.stream() // .map(packageLookup) // diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index c4691503ba1f..509467bf108a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -56,7 +56,7 @@ class DefaultLauncher implements Launcher { */ DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters, NamespacedHierarchicalStore sessionLevelStore) { - Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), + Preconditions.condition(testEngines.iterator().hasNext(), () -> "Cannot create Launcher without at least one TestEngine; " + "consider adding an engine implementation JAR to the classpath"); Preconditions.notNull(postDiscoveryFilters, "PostDiscoveryFilter array must not be null"); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java index e78e720272dd..7714d3fb7618 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Locale; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.ConfigurationParameters; @@ -77,7 +78,7 @@ else if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelecto } } - static TestSource toSource(DiscoverySelector selector) { + static @Nullable TestSource toSource(DiscoverySelector selector) { if (selector instanceof ClassSelector classSelector) { return ClassSource.from(classSelector.getClassName()); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java index f7b3abe14c22..6dbae69407c3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueNotifier.java @@ -21,6 +21,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; @@ -42,15 +43,21 @@ class DiscoveryIssueNotifier { private final List criticalIssues; private final List nonCriticalIssues; + @SuppressWarnings("NullAway") static DiscoveryIssueNotifier from(Severity criticalSeverity, List issues) { - Map> issuesByCriticality = issues.stream() // - .sorted(comparing(DiscoveryIssue::severity).reversed()) // - .collect(partitioningBy(issue -> issue.severity().compareTo(criticalSeverity) >= 0)); + var issuesByCriticality = partitionByCriticality(criticalSeverity, issues); List criticalIssues = issuesByCriticality.get(true); List nonCriticalIssues = issuesByCriticality.get(false); return new DiscoveryIssueNotifier(new ArrayList<>(issues), criticalIssues, nonCriticalIssues); } + private static Map> partitionByCriticality(Severity criticalSeverity, + List issues) { + return issues.stream() // + .sorted(comparing(DiscoveryIssue::severity).reversed()) // + .collect(partitioningBy(issue -> issue.severity().compareTo(criticalSeverity) >= 0)); + } + private DiscoveryIssueNotifier(List allIssues, List criticalIssues, List nonCriticalIssues) { this.allIssues = allIssues; @@ -74,6 +81,7 @@ void logNonCriticalIssues(TestEngine testEngine) { logIssues(testEngine, nonCriticalIssues, "non-critical"); } + @Nullable DiscoveryIssueException createExceptionForCriticalIssues(TestEngine testEngine) { if (criticalIssues.isEmpty()) { return null; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java index c5a9772d67a0..b836d2ca5b3c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java @@ -17,6 +17,7 @@ import java.util.function.Supplier; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId.Segment; @@ -34,6 +35,8 @@ class HierarchicalOutputDirectoryProvider implements OutputDirectoryProvider { private static final String REPLACEMENT = "_"; private final Supplier rootDirSupplier; + + @Nullable private volatile Path rootDir; HierarchicalOutputDirectoryProvider(Supplier rootDirSupplier) { @@ -54,10 +57,12 @@ public Path createOutputDirectory(TestDescriptor testDescriptor) throws IOExcept @Override public synchronized Path getRootDirectory() { - if (rootDir == null) { - rootDir = rootDirSupplier.get(); + var currentRootDir = rootDir; + if (currentRootDir == null) { + currentRootDir = rootDirSupplier.get(); + rootDir = currentRootDir; } - return rootDir; + return currentRootDir; } private static Path toSanitizedPath(Segment segment) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java index 44656ff04a51..063b60a51d22 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java @@ -34,6 +34,7 @@ public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { } @Override + @SuppressWarnings("NullAway") public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { interceptor.intercept(() -> { super.execute(launcherDiscoveryRequest, listeners); @@ -42,6 +43,7 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu } @Override + @SuppressWarnings("NullAway") public void execute(TestPlan testPlan, TestExecutionListener... listeners) { interceptor.intercept(() -> { super.execute(testPlan, listeners); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java index 9523a05e9b75..ebb0f19a3df4 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; @@ -70,7 +71,7 @@ public Set keySet() { Collectors.toSet()); } - private String getProperty(String key) { + private @Nullable String getProperty(String key) { Preconditions.notBlank(key, "key must not be null or blank"); return providers.stream() // .map(parameterProvider -> parameterProvider.getValue(key)) // @@ -92,6 +93,8 @@ static final class Builder { private final List configResources = new ArrayList<>(); private boolean implicitProvidersEnabled = true; private String configFileName = ConfigurationParameters.CONFIG_FILE_NAME; + + @Nullable private ConfigurationParameters parentConfigurationParameters; private Builder() { @@ -149,6 +152,7 @@ LauncherConfigurationParameters build() { private interface ParameterProvider { + @Nullable String getValue(String key); default int size() { @@ -160,7 +164,7 @@ default int size() { static ParameterProvider explicit(Map configParams) { return new ParameterProvider() { @Override - public String getValue(String key) { + public @Nullable String getValue(String key) { return configParams.get(key); } @@ -186,7 +190,7 @@ public String toString() { static ParameterProvider systemProperties() { return new ParameterProvider() { @Override - public String getValue(String key) { + public @Nullable String getValue(String key) { try { return System.getProperty(key); } @@ -233,7 +237,7 @@ public String toString() { static ParameterProvider inherited(ConfigurationParameters configParams) { return new ParameterProvider() { @Override - public String getValue(String key) { + public @Nullable String getValue(String key) { return configParams.get(key).orElse(null); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index 8154f4024d25..8f821985f3d6 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -22,6 +22,7 @@ import java.util.Map; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; @@ -109,7 +110,11 @@ public final class LauncherDiscoveryRequestBuilder { private final List configurationParametersResources = new ArrayList<>(); private final List discoveryListeners = new ArrayList<>(); private boolean implicitConfigurationParametersEnabled = true; + + @Nullable private ConfigurationParameters parentConfigurationParameters; + + @Nullable private OutputDirectoryProvider outputDirectoryProvider; /** diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java index 7b3db9368915..9be73b2ed4c8 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java @@ -11,6 +11,7 @@ package org.junit.platform.launcher.core; import static java.util.Collections.unmodifiableMap; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toMap; import static org.apiguardian.api.API.Status.INTERNAL; @@ -23,6 +24,7 @@ import java.util.stream.Collectors; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.TestDescriptor; @@ -59,7 +61,7 @@ public List getDiscoveryIssues(TestEngine testEngine) { } EngineResultInfo getEngineResult(TestEngine testEngine) { - return this.testEngineResults.get(testEngine); + return requireNonNull(this.testEngineResults.get(testEngine)); } ConfigurationParameters getConfigurationParameters() { @@ -116,11 +118,14 @@ static EngineResultInfo errored(TestDescriptor rootDescriptor, DiscoveryIssueNot } private final TestDescriptor rootDescriptor; + + @Nullable private final Throwable cause; + private final DiscoveryIssueNotifier discoveryIssueNotifier; EngineResultInfo(TestDescriptor rootDescriptor, DiscoveryIssueNotifier discoveryIssueNotifier, - Throwable cause) { + @Nullable Throwable cause) { this.rootDescriptor = rootDescriptor; this.discoveryIssueNotifier = discoveryIssueNotifier; this.cause = cause; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java index 7bdfe458e03f..4ea6111553ce 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java @@ -10,6 +10,11 @@ package org.junit.platform.launcher.core; +import static java.util.Objects.requireNonNull; + +import java.util.Optional; + +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; @@ -25,8 +30,14 @@ class OutcomeDelayingEngineExecutionListener extends DelegatingEngineExecutionLi private final TestDescriptor engineDescriptor; private volatile boolean engineStarted; + + @Nullable private volatile Outcome outcome; + + @Nullable private volatile String skipReason; + + @Nullable private volatile TestExecutionResult executionResult; OutcomeDelayingEngineExecutionListener(EngineExecutionListener delegate, TestDescriptor engineDescriptor) { @@ -66,10 +77,10 @@ public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult void reportEngineOutcome() { if (outcome == Outcome.FINISHED) { - super.executionFinished(engineDescriptor, executionResult); + super.executionFinished(engineDescriptor, requireNonNull(executionResult)); } else if (outcome == Outcome.SKIPPED) { - super.executionSkipped(engineDescriptor, skipReason); + super.executionSkipped(engineDescriptor, requireNonNull(skipReason)); } } @@ -80,9 +91,9 @@ void reportEngineStartIfNecessary() { } void reportEngineFailure(Throwable throwable) { - if (executionResult != null && executionResult.getThrowable().isPresent()) { - throwable.addSuppressed(executionResult.getThrowable().get()); - } + Optional.ofNullable(this.executionResult) // + .flatMap(TestExecutionResult::getThrowable) // + .ifPresent(throwable::addSuppressed); super.executionFinished(engineDescriptor, TestExecutionResult.failed(throwable)); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java index ebab21e42302..f090b2bbdf12 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java @@ -18,6 +18,7 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; @@ -45,7 +46,7 @@ static Iterable load(@SuppressWarnings("SameParameterValue") Class typ return load(type, collectingClassNameFilter, instances -> logLoadedInstances(type, instances, exclusions)); } - private static String logLoadedInstances(Class type, List instances, List exclusions) { + private static String logLoadedInstances(Class type, List instances, @Nullable List exclusions) { String typeName = type.getSimpleName(); if (exclusions == null) { return "Loaded %s instances: %s".formatted(typeName, instances); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java index 85b69f64bb26..60a3d6a8cc9a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java @@ -18,6 +18,8 @@ import java.util.concurrent.ConcurrentLinkedDeque; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; + /** * @since 1.3 */ @@ -112,7 +114,7 @@ private void pushToTop(RewindableByteArrayOutputStream out) { } } - private RewindableByteArrayOutputStream getOutput() { + private @Nullable RewindableByteArrayOutputStream getOutput() { RewindableByteArrayOutputStream out = output.get(); return out.isMarked() ? out : mostRecentOutputs.peek(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java index 779b81d67b93..fca61aab1372 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/package-info.java @@ -5,4 +5,7 @@ * LauncherDiscoveryRequestBuilder}. */ +@NullMarked package org.junit.platform.launcher.core; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java index eb0fd73cdd74..a17b48147a40 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java @@ -18,6 +18,7 @@ import java.util.logging.Logger; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; @@ -82,7 +83,7 @@ public static LoggingListener forBiConsumer(BiConsumer> logger; + private final BiConsumer<@Nullable Throwable, Supplier> logger; private LoggingListener(BiConsumer> logger) { this.logger = Preconditions.notNull(logger, "logger must not be null"); @@ -123,7 +124,7 @@ private void log(String message, Object... args) { logWithThrowable(message, null, args); } - private void logWithThrowable(String message, Throwable t, Object... args) { + private void logWithThrowable(String message, @Nullable Throwable t, Object... args) { this.logger.accept(t, () -> message.formatted(args)); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java index 0ef6826b016b..f53d3381756d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java @@ -250,7 +250,7 @@ private void printStackTrace(PrintWriter writer, StackTraceElement[] parentTrace seenThrowables.add(throwable); StackTraceElement[] trace = throwable.getStackTrace(); - if (parentTrace != null && parentTrace.length > 0) { + if (parentTrace.length > 0) { writer.printf("%s%s%s%n", indentation, caption, throwable); } int duplicates = numberOfCommonFrames(trace, parentTrace); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java index 751888b2ae86..49d20e0331aa 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java @@ -10,13 +10,16 @@ package org.junit.platform.launcher.listeners; +import static java.util.Objects.requireNonNull; import static java.util.stream.Stream.concat; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -32,7 +35,10 @@ @API(status = MAINTAINED, since = "1.0") public class SummaryGeneratingListener implements TestExecutionListener { + @Nullable private TestPlan testPlan; + + @Nullable private MutableTestExecutionSummary summary; public SummaryGeneratingListener() { @@ -43,7 +49,11 @@ public SummaryGeneratingListener() { * Get the summary generated by this listener. */ public TestExecutionSummary getSummary() { - return this.summary; + return getMutableSummary(); + } + + private MutableTestExecutionSummary getMutableSummary() { + return Preconditions.notNull(this.summary, "No tests have yet been executed"); } @Override @@ -54,22 +64,25 @@ public void testPlanExecutionStarted(TestPlan testPlan) { @Override public void testPlanExecutionFinished(TestPlan testPlan) { - this.summary.timeFinished = System.currentTimeMillis(); - this.summary.timeFinishedNanos = System.nanoTime(); + var summary = getMutableSummary(); + summary.timeFinished = System.currentTimeMillis(); + summary.timeFinishedNanos = System.nanoTime(); } @Override public void dynamicTestRegistered(TestIdentifier testIdentifier) { + var summary = getMutableSummary(); if (testIdentifier.isContainer()) { - this.summary.containersFound.incrementAndGet(); + summary.containersFound.incrementAndGet(); } if (testIdentifier.isTest()) { - this.summary.testsFound.incrementAndGet(); + summary.testsFound.incrementAndGet(); } } @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { + var testPlan = requireNonNull(this.testPlan); // @formatter:off long skippedContainers = concat(Stream.of(testIdentifier), testPlan.getDescendants(testIdentifier).stream()) .filter(TestIdentifier::isContainer) @@ -78,54 +91,57 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) { .filter(TestIdentifier::isTest) .count(); // @formatter:on - this.summary.containersSkipped.addAndGet(skippedContainers); - this.summary.testsSkipped.addAndGet(skippedTests); + var summary = getMutableSummary(); + summary.containersSkipped.addAndGet(skippedContainers); + summary.testsSkipped.addAndGet(skippedTests); } @Override public void executionStarted(TestIdentifier testIdentifier) { + var summary = getMutableSummary(); if (testIdentifier.isContainer()) { - this.summary.containersStarted.incrementAndGet(); + summary.containersStarted.incrementAndGet(); } if (testIdentifier.isTest()) { - this.summary.testsStarted.incrementAndGet(); + summary.testsStarted.incrementAndGet(); } } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + var summary = getMutableSummary(); switch (testExecutionResult.getStatus()) { case SUCCESSFUL: { if (testIdentifier.isContainer()) { - this.summary.containersSucceeded.incrementAndGet(); + summary.containersSucceeded.incrementAndGet(); } if (testIdentifier.isTest()) { - this.summary.testsSucceeded.incrementAndGet(); + summary.testsSucceeded.incrementAndGet(); } break; } case ABORTED: { if (testIdentifier.isContainer()) { - this.summary.containersAborted.incrementAndGet(); + summary.containersAborted.incrementAndGet(); } if (testIdentifier.isTest()) { - this.summary.testsAborted.incrementAndGet(); + summary.testsAborted.incrementAndGet(); } break; } case FAILED: { if (testIdentifier.isContainer()) { - this.summary.containersFailed.incrementAndGet(); + summary.containersFailed.incrementAndGet(); } if (testIdentifier.isTest()) { - this.summary.testsFailed.incrementAndGet(); + summary.testsFailed.incrementAndGet(); } testExecutionResult.getThrowable().ifPresent( - throwable -> this.summary.addFailure(testIdentifier, throwable)); + throwable -> summary.addFailure(testIdentifier, throwable)); break; } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java index e25e4319da31..2c05881a5bfb 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.listeners; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.STABLE; import java.io.IOException; @@ -23,6 +24,7 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.ConfigurationParameters; @@ -134,6 +136,8 @@ public class UniqueIdTrackingListener implements TestExecutionListener { private final List uniqueIds = new ArrayList<>(); private boolean enabled; + + @Nullable private TestPlan testPlan; public UniqueIdTrackingListener() { @@ -165,7 +169,7 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult private void trackTestUidRecursively(TestIdentifier testIdentifier) { boolean tracked = trackTestUid(testIdentifier); if (!tracked) { - this.testPlan.getChildren(testIdentifier).forEach(this::trackTestUidRecursively); + requireNonNull(this.testPlan).getChildren(testIdentifier).forEach(this::trackTestUidRecursively); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java index 371df34d4fb3..de2821f42f91 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/package-info.java @@ -6,4 +6,7 @@ * @since 1.6 */ +@NullMarked package org.junit.platform.launcher.listeners.discovery; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java index d8a0a06ea0a9..e662ea8e912e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/package-info.java @@ -4,4 +4,7 @@ * the {@link org.junit.platform.launcher.Launcher Launcher}. */ +@NullMarked package org.junit.platform.launcher.listeners; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java index 97541a153cba..7c6f4f5167ac 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/package-info.java @@ -6,4 +6,7 @@ * @since 1.8 */ +@NullMarked package org.junit.platform.launcher.listeners.session; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java index 4005a8426267..4101c31e89d5 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/package-info.java @@ -4,4 +4,7 @@ *

This API is typically used by IDEs and build tools. */ +@NullMarked package org.junit.platform.launcher; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java index 4d3899199cee..6cff93dd5af6 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java @@ -13,6 +13,8 @@ import java.util.ArrayDeque; import java.util.Deque; +import org.junit.platform.commons.util.Preconditions; + /** * @since 1.1 */ @@ -27,12 +29,12 @@ public void push(T t) { @Override public T peek() { - return deque.peek(); + return Preconditions.notNull(deque.peek(), () -> "stack is empty"); } @Override public T pop() { - return deque.pollFirst(); + return Preconditions.notNull(deque.pollFirst(), () -> "stack is empty"); } @Override diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java index 7adc69050a06..1d6731b7f2e3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.tagexpression; +import static java.util.Objects.requireNonNull; import static org.junit.platform.launcher.tagexpression.Operator.Associativity.Left; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingOperatorBetween; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingRhsOperand; @@ -19,6 +20,8 @@ import java.util.function.BiFunction; import java.util.function.Function; +import org.jspecify.annotations.Nullable; + /** * @since 1.1 */ @@ -36,6 +39,7 @@ static Operator nullaryOperator(String representation, int precedence) { return new Operator(representation, precedence, 0, null, (expressions, operatorToken) -> success()); } + @SuppressWarnings("SameParameterValue") static Operator unaryOperator(String representation, int precedence, Associativity associativity, Function unaryExpression) { @@ -50,6 +54,7 @@ static Operator unaryOperator(String representation, int precedence, Associativi }); } + @SuppressWarnings("SameParameterValue") static Operator binaryOperator(String representation, int precedence, Associativity associativity, BiFunction binaryExpression) { @@ -75,10 +80,13 @@ static Operator binaryOperator(String representation, int precedence, Associativ private final String representation; private final int precedence; private final int arity; + + @Nullable private final Associativity associativity; + private final TagExpressionCreator tagExpressionCreator; - private Operator(String representation, int precedence, int arity, Associativity associativity, + private Operator(String representation, int precedence, int arity, @Nullable Associativity associativity, TagExpressionCreator tagExpressionCreator) { this.representation = representation; @@ -126,7 +134,7 @@ private String createMissingOperandMessage(Stack> expre if (2 == mismatch) { return "missing lhs and rhs operand"; } - return missingOneOperand(operatorToken.isLeftOf(expressions.peek().token) ? "lhs" : "rhs"); + return missingOneOperand(operatorToken.isLeftOf(requireNonNull(expressions.peek()).token) ? "lhs" : "rhs"); } return "missing operand"; } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java index 541ceb976bb1..3ea407374d4c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java @@ -18,6 +18,8 @@ import java.util.Map; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + /** * @since 1.1 */ @@ -30,10 +32,7 @@ class Operators { private final Map representationToOperator = Stream.of(Not, And, Or).collect( toMap(Operator::representation, identity())); - boolean isOperator(String token) { - return representationToOperator.containsKey(token); - } - + @Nullable Operator operatorFor(String token) { return representationToOperator.get(token); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java index 0882acda8ede..befc5227eaaa 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java @@ -41,7 +41,7 @@ default TagExpression tagExpressionOrThrow(Function ex if (errorMessage().isPresent()) { throw exceptionCreator.apply(errorMessage().get()); } - return tagExpression().get(); + return tagExpression().orElseThrow(); } /** diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java index 695ef20c70ac..06aa9db6703b 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java @@ -12,6 +12,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; + /** * @since 1.1 */ @@ -56,13 +58,14 @@ private static String format(int indexInTagExpression) { return "<" + indexInTagExpression + ">"; } - private static ParseStatus error(String errorMessage) { + private static ParseStatus error(@Nullable String errorMessage) { return new ParseStatus(errorMessage); } + @Nullable final String errorMessage; - private ParseStatus(String errorMessage) { + private ParseStatus(@Nullable String errorMessage) { this.errorMessage = errorMessage; } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java index 417cf3e52aad..5605ba4ac3a2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java @@ -12,6 +12,8 @@ import java.util.List; +import org.jspecify.annotations.Nullable; + /** * @since 1.1 */ @@ -19,11 +21,11 @@ class Parser { private final Tokenizer tokenizer = new Tokenizer(); - ParseResult parse(String infixTagExpression) { + ParseResult parse(@Nullable String infixTagExpression) { return constructExpressionFrom(tokensDerivedFrom(infixTagExpression)); } - private List tokensDerivedFrom(String infixTagExpression) { + private List tokensDerivedFrom(@Nullable String infixTagExpression) { return tokenizer.tokenize(infixTagExpression); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java index b54d40f5b6a9..3808ad6c6f5b 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java @@ -11,6 +11,7 @@ package org.junit.platform.launcher.tagexpression; import static java.lang.Integer.MIN_VALUE; +import static java.util.Objects.requireNonNull; import static org.junit.platform.launcher.tagexpression.Operator.nullaryOperator; import static org.junit.platform.launcher.tagexpression.ParseStatus.emptyTagExpression; import static org.junit.platform.launcher.tagexpression.ParseStatus.missingClosingParenthesis; @@ -53,7 +54,7 @@ public ParseResult execute() { .process(this::ensureOnlySingleExpressionRemains); // @formatter:on if (parseStatus.isError()) { - return ParseResults.error(parseStatus.errorMessage); + return ParseResults.error(requireNonNull(parseStatus.errorMessage)); } return ParseResults.success(expressions.pop().element); } @@ -75,8 +76,8 @@ private ParseStatus process(Token token) { if (RightParenthesis.represents(trimmed)) { return findMatchingLeftParenthesis(token); } - if (validOperators.isOperator(trimmed)) { - Operator operator = validOperators.operatorFor(trimmed); + var operator = validOperators.operatorFor(trimmed); + if (operator != null) { return findOperands(token, operator); } pushExpressionAt(token, convertLeafTokenToExpression(trimmed)); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java index ac101b2a6810..46f04acee8d1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java @@ -15,6 +15,7 @@ import java.util.Collection; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestTag; /** @@ -30,11 +31,12 @@ public interface TagExpression { * Attempt to parse a {@link TagExpression} from the supplied tag * expression string. * - * @param infixTagExpression the tag expression string to parse; never {@code null}. + * @param infixTagExpression the tag expression string to parse; may be + * {@code null}. * @see ParseResult */ @API(status = INTERNAL, since = "1.1") - static ParseResult parseFrom(String infixTagExpression) { + static ParseResult parseFrom(@Nullable String infixTagExpression) { return new Parser().parse(infixTagExpression); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java index f21dd73aa332..042f97eb12b1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java @@ -18,6 +18,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; + /** * @since 1.1 */ @@ -26,7 +28,7 @@ class Tokenizer { private static final Pattern PATTERN = Pattern.compile("\\s*(?:(?:(?:any|none)\\(\\))|[()!|&]|(?:[^\\s()!|&]+))", CASE_INSENSITIVE); - List tokenize(String infixTagExpression) { + List tokenize(@Nullable String infixTagExpression) { if (infixTagExpression == null) { return emptyList(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java index 53e2a7fac56d..efab367f98dc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/package-info.java @@ -2,4 +2,7 @@ * The tag expression language parser and related support classes. */ +@NullMarked package org.junit.platform.launcher.tagexpression; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts index ebf83f3e7a9b..81f6274eb72a 100644 --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -2,6 +2,7 @@ import junitbuild.extensions.javaModuleName plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") id("junitbuild.shadow-conventions") `java-test-fixtures` } @@ -13,6 +14,8 @@ dependencies { api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) + compileOnlyApi(libs.openTestReporting.tooling.spi) shadowed(libs.openTestReporting.events) diff --git a/junit-platform-reporting/src/main/java/module-info.java b/junit-platform-reporting/src/main/java/module-info.java index f1fe8cd51dca..11202c5829c3 100644 --- a/junit-platform-reporting/src/main/java/module-info.java +++ b/junit-platform-reporting/src/main/java/module-info.java @@ -14,8 +14,11 @@ * @since 1.4 */ module org.junit.platform.reporting { - requires java.xml; + requires static transitive org.apiguardian.api; + requires static org.jspecify; + + requires java.xml; requires org.junit.platform.commons; requires transitive org.junit.platform.engine; requires transitive org.junit.platform.launcher; diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java index 1f76a53869b1..b82ac7c9f1a8 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.launcher.TestIdentifier; @@ -62,11 +63,11 @@ public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifi return getParentLegacyReportingName(testPlan, testIdentifier); } - private static TestIdentifier getParent(TestPlan testPlan, TestIdentifier testIdentifier) { + private static @Nullable TestIdentifier getParent(TestPlan testPlan, TestIdentifier testIdentifier) { return testPlan.getParent(testIdentifier).orElse(null); } - private static ClassSource getClassSource(TestIdentifier current) { + private static @Nullable ClassSource getClassSource(TestIdentifier current) { // @formatter:off return current.getSource() .filter(ClassSource.class::isInstance) diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java index f7a50ee115f0..af41a0a5efbe 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/package-info.java @@ -2,4 +2,7 @@ * Support for legacy reporting formats. */ +@NullMarked package org.junit.platform.reporting.legacy; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java index 4d885ee80c2d..4287192fa408 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java @@ -10,6 +10,7 @@ package org.junit.platform.reporting.legacy.xml; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.STABLE; import java.io.IOException; @@ -22,6 +23,7 @@ import javax.xml.stream.XMLStreamException; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; @@ -48,6 +50,7 @@ public class LegacyXmlReportGeneratingListener implements TestExecutionListener private final PrintWriter out; private final Clock clock; + @Nullable private XmlReportData reportData; public LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out) { @@ -83,23 +86,23 @@ public void testPlanExecutionFinished(TestPlan testPlan) { @Override public void executionSkipped(TestIdentifier testIdentifier, String reason) { - this.reportData.markSkipped(testIdentifier, reason); + requiredReportData().markSkipped(testIdentifier, reason); writeXmlReportInCaseOfRoot(testIdentifier); } @Override public void executionStarted(TestIdentifier testIdentifier) { - this.reportData.markStarted(testIdentifier); + requiredReportData().markStarted(testIdentifier); } @Override public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { - this.reportData.addReportEntry(testIdentifier, entry); + requiredReportData().addReportEntry(testIdentifier, entry); } @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult result) { - this.reportData.markFinished(testIdentifier, result); + requiredReportData().markFinished(testIdentifier, result); writeXmlReportInCaseOfRoot(testIdentifier); } @@ -113,13 +116,17 @@ private void writeXmlReportInCaseOfRoot(TestIdentifier testIdentifier) { private void writeXmlReportSafely(TestIdentifier testIdentifier, String rootName) { Path xmlFile = this.reportsDir.resolve("TEST-" + rootName + ".xml"); try (Writer fileWriter = Files.newBufferedWriter(xmlFile)) { - new XmlReportWriter(this.reportData).writeXmlReport(testIdentifier, fileWriter); + new XmlReportWriter(requiredReportData()).writeXmlReport(testIdentifier, fileWriter); } catch (XMLStreamException | IOException e) { printException("Could not write XML report: " + xmlFile, e); } } + private XmlReportData requiredReportData() { + return requireNonNull(this.reportData); + } + private boolean isRoot(TestIdentifier testIdentifier) { return testIdentifier.getParentIdObject().isEmpty(); } diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java index f1ce85be73cb..477c314d2541 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; @@ -60,7 +61,7 @@ Clock getClock() { return this.clock; } - void markSkipped(TestIdentifier testIdentifier, String reason) { + void markSkipped(TestIdentifier testIdentifier, @Nullable String reason) { this.skippedTests.put(testIdentifier, reason == null ? "" : reason); } @@ -94,6 +95,7 @@ boolean wasSkipped(TestIdentifier testIdentifier) { return Duration.between(startInstant, endInstant).toMillis() / (double) MILLIS_PER_SECOND; } + @Nullable String getSkipReason(TestIdentifier testIdentifier) { return findSkippedAncestor(testIdentifier).map(skippedTestIdentifier -> { String reason = this.skippedTests.get(skippedTestIdentifier); 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 d3ae6b008bd9..203cac3e3f15 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 @@ -15,6 +15,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableMap; 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; @@ -55,6 +56,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; @@ -244,10 +246,10 @@ private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, } } - private void writeSkippedElement(String reason, XMLStreamWriter writer) throws XMLStreamException { + private void writeSkippedElement(@Nullable String reason, XMLStreamWriter writer) throws XMLStreamException { if (isNotBlank(reason)) { writer.writeStartElement("skipped"); - writeCDataSafely(reason); + writeCDataSafely(requireNonNull(reason)); writer.writeEndElement(); } else { @@ -256,7 +258,7 @@ private void writeSkippedElement(String reason, XMLStreamWriter writer) throws X newLine(); } - private void writeErrorOrFailureElement(Type type, Throwable throwable, XMLStreamWriter writer) + private void writeErrorOrFailureElement(Type type, @Nullable Throwable throwable, XMLStreamWriter writer) throws XMLStreamException { String elementName = type == FAILURE ? "failure" : "error"; diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java index d41e891b296a..bdec16594877 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/package-info.java @@ -4,4 +4,7 @@ * by the Ant build system. */ +@NullMarked package org.junit.platform.reporting.legacy.xml; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java index 2fae334d983a..4726702e5a30 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java @@ -10,6 +10,7 @@ package org.junit.platform.reporting.open.xml; +import static java.util.Objects.requireNonNull; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; @@ -71,6 +72,7 @@ import java.util.function.BiConsumer; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; @@ -114,6 +116,8 @@ public class OpenTestReportGeneratingListener implements TestExecutionListener { private final Map inProgressIds = new ConcurrentHashMap<>(); private DocumentWriter eventsFileWriter = DocumentWriter.noop(); private final Path workingDir; + + @Nullable private Path outputDir; @SuppressWarnings("unused") // Used via ServiceLoader @@ -361,7 +365,8 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e })); } - private static void attachOutput(Attachments attachments, LocalDateTime timestamp, String content, String source) { + private static void attachOutput(Attachments attachments, LocalDateTime timestamp, @Nullable String content, + String source) { if (content != null) { attachments.append(output(timestamp), output -> output.withSource(source).withContent(content)); } @@ -373,7 +378,7 @@ public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry entry) { eventsFileWriter.append(reported(id, Instant.now()), // reported -> reported.append(attachments(), attachments -> attachments.append(file(entry.getTimestamp()), // file -> { - file.withPath(outputDir.relativize(entry.getPath()).toString()); + file.withPath(requireNonNull(outputDir).relativize(entry.getPath()).toString()); entry.getMediaType().ifPresent(file::withMediaType); }))); } diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java index 5befd7af7b57..15a76f02d2c6 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/package-info.java @@ -2,4 +2,7 @@ * Support for generating Open Test Reporting compatible XML event reports. */ +@NullMarked package org.junit.platform.reporting.open.xml; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java index d7c603241d19..09fb2bd2538e 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/package-info.java @@ -2,4 +2,7 @@ * JUnit Platform Reporting. */ +@NullMarked package org.junit.platform.reporting; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-suite-api/junit-platform-suite-api.gradle.kts b/junit-platform-suite-api/junit-platform-suite-api.gradle.kts index cc5eb1d08bb7..a5f73150fae6 100644 --- a/junit-platform-suite-api/junit-platform-suite-api.gradle.kts +++ b/junit-platform-suite-api/junit-platform-suite-api.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") } description = "JUnit Platform Suite API" @@ -9,6 +10,7 @@ dependencies { api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-platform-suite-api/src/main/java/module-info.java b/junit-platform-suite-api/src/main/java/module-info.java index 9d8df8da3cf0..690db0fe2d21 100644 --- a/junit-platform-suite-api/src/main/java/module-info.java +++ b/junit-platform-suite-api/src/main/java/module-info.java @@ -14,7 +14,10 @@ * @since 1.0 */ module org.junit.platform.suite.api { + requires static transitive org.apiguardian.api; + requires static org.jspecify; + requires transitive org.junit.platform.commons; exports org.junit.platform.suite.api; diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java index 1e54ed53ef68..7463a9aa06e8 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/package-info.java @@ -6,4 +6,7 @@ * {@code junit-platform-suite-engine} module. */ +@NullMarked package org.junit.platform.suite.api; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts b/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts index fdc4062f36ce..cb43e5d6963c 100644 --- a/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts +++ b/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") } description = "JUnit Platform Suite Commons" @@ -9,6 +10,7 @@ dependencies { api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) implementation(projects.junitPlatformEngine) implementation(projects.junitPlatformSuiteApi) diff --git a/junit-platform-suite-commons/src/main/java/module-info.java b/junit-platform-suite-commons/src/main/java/module-info.java index 52c508bdace7..f0514a14064d 100644 --- a/junit-platform-suite-commons/src/main/java/module-info.java +++ b/junit-platform-suite-commons/src/main/java/module-info.java @@ -14,7 +14,10 @@ * @since 1.8 */ module org.junit.platform.suite.commons { + requires static transitive org.apiguardian.api; + requires static org.jspecify; + requires org.junit.platform.suite.api; requires org.junit.platform.commons; requires org.junit.platform.engine; diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java index 292a2aea605e..8176e6b070a5 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java +++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java @@ -32,6 +32,7 @@ import java.util.stream.Stream; 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.junit.platform.engine.ConfigurationParameters; @@ -120,7 +121,10 @@ public final class SuiteLauncherDiscoveryRequestBuilder { private final Set selectedClassNames = new LinkedHashSet<>(); private boolean includeClassNamePatternsUsed; private boolean filterStandardClassNamePatterns = false; + + @Nullable private ConfigurationParameters parentConfigurationParameters; + private boolean enableParentConfigurationParameters = true; private SuiteLauncherDiscoveryRequestBuilder() { @@ -516,14 +520,16 @@ private static MethodSelector toMethodSelectorFromFQMN(Class suiteClass, Sele return DiscoverySelectors.selectMethod(annotation.value()); } - private static MethodSelector toMethodSelector(Class suiteClass, Class type, String typeName, - Class[] parameterTypes, String methodName, String parameterTypeNames) { + private static MethodSelector toMethodSelector(Class suiteClass, @Nullable Class type, + @Nullable String typeName, Class @Nullable [] parameterTypes, String methodName, + String parameterTypeNames) { if (type == null) { - Preconditions.notBlank(typeName, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, - "type must be set or type name must not be blank")); + String nonBlankTypeName = Preconditions.notBlank(typeName, + () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, + "type must be set or type name must not be blank")); return parameterTypes == null // - ? DiscoverySelectors.selectMethod(typeName, methodName, parameterTypeNames) // - : DiscoverySelectors.selectMethod(typeName, methodName, parameterTypes); + ? DiscoverySelectors.selectMethod(nonBlankTypeName, methodName, parameterTypeNames) // + : DiscoverySelectors.selectMethod(nonBlankTypeName, methodName, parameterTypes); } else { Preconditions.condition(typeName == null, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java index 3eeb635013aa..422ebf466472 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java +++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java @@ -8,4 +8,7 @@ * APIs by external parties is not supported! */ +@NullMarked package org.junit.platform.suite.commons; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts index 36abcdbc088d..7e777400ca8b 100644 --- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") } description = "JUnit Platform Suite Engine" @@ -10,6 +11,7 @@ dependencies { api(projects.junitPlatformSuiteApi) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) implementation(projects.junitPlatformSuiteCommons) diff --git a/junit-platform-suite-engine/src/main/java/module-info.java b/junit-platform-suite-engine/src/main/java/module-info.java index e8a43bf80e1c..e7e15543dba2 100644 --- a/junit-platform-suite-engine/src/main/java/module-info.java +++ b/junit-platform-suite-engine/src/main/java/module-info.java @@ -16,7 +16,10 @@ * @provides org.junit.platform.engine.TestEngine */ module org.junit.platform.suite.engine { + requires static org.apiguardian.api; + requires static org.jspecify; + requires org.junit.platform.suite.api; requires org.junit.platform.suite.commons; requires org.junit.platform.commons; diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java index 0ab81c9e3836..d5e474519108 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java @@ -10,6 +10,7 @@ package org.junit.platform.suite.engine; +import static java.util.Objects.requireNonNull; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.joining; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; @@ -21,6 +22,7 @@ import java.util.function.BiFunction; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; @@ -71,7 +73,10 @@ final class SuiteTestDescriptor extends AbstractTestDescriptor { private final Class suiteClass; private final LifecycleMethods lifecycleMethods; + @Nullable private LauncherDiscoveryResult launcherDiscoveryResult; + + @Nullable private SuiteLauncher launcher; SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters, @@ -174,7 +179,7 @@ private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) { } } - private TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener, + private @Nullable TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener, NamespacedHierarchicalStore requestLevelStore, ThrowableCollector throwableCollector) { if (throwableCollector.isNotEmpty()) { return null; @@ -183,9 +188,9 @@ private TestExecutionSummary executeTests(EngineExecutionListener parentEngineEx // #2838: The discovery result from a suite may have been filtered by // post discovery filters from the launcher. The discovery result should // be pruned accordingly. - LauncherDiscoveryResult discoveryResult = this.launcherDiscoveryResult.withRetainedEngines( + LauncherDiscoveryResult discoveryResult = requireNonNull(this.launcherDiscoveryResult).withRetainedEngines( getChildren()::contains); - return launcher.execute(discoveryResult, parentEngineExecutionListener, requestLevelStore); + return requireNonNull(launcher).execute(discoveryResult, parentEngineExecutionListener, requestLevelStore); } private void executeAfterSuiteMethods(ThrowableCollector throwableCollector) { @@ -194,12 +199,13 @@ private void executeAfterSuiteMethods(ThrowableCollector throwableCollector) { } } - private TestExecutionResult computeTestExecutionResult(TestExecutionSummary summary, + private TestExecutionResult computeTestExecutionResult(@Nullable TestExecutionSummary summary, ThrowableCollector throwableCollector) { - if (throwableCollector.isNotEmpty()) { - return TestExecutionResult.failed(throwableCollector.getThrowable()); + var throwable = throwableCollector.getThrowable(); + if (throwable != null) { + return TestExecutionResult.failed(throwable); } - if (failIfNoTests && summary.getTestsFoundCount() == 0) { + if (failIfNoTests && requireNonNull(summary).getTestsFoundCount() == 0) { return TestExecutionResult.failed(new NoTestsDiscoveredException(suiteClass)); } return TestExecutionResult.successful(); diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java index fa4237d3cec4..09748becae7e 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/package-info.java @@ -2,4 +2,7 @@ * Core package for the JUnit Platform Suite test engine. */ +@NullMarked package org.junit.platform.suite.engine; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-testkit/junit-platform-testkit.gradle.kts b/junit-platform-testkit/junit-platform-testkit.gradle.kts index 38e89344db34..7cb29ac6f662 100644 --- a/junit-platform-testkit/junit-platform-testkit.gradle.kts +++ b/junit-platform-testkit/junit-platform-testkit.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") } description = "JUnit Platform Test Kit" @@ -11,6 +12,7 @@ dependencies { api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-platform-testkit/src/main/java/module-info.java b/junit-platform-testkit/src/main/java/module-info.java index 58f2033bcf62..cbfbd2a41393 100644 --- a/junit-platform-testkit/src/main/java/module-info.java +++ b/junit-platform-testkit/src/main/java/module-info.java @@ -15,7 +15,10 @@ * @uses org.junit.platform.engine.TestEngine */ module org.junit.platform.testkit { + requires static transitive org.apiguardian.api; + requires static org.jspecify; + requires transitive org.assertj.core; requires org.junit.platform.commons; requires transitive org.junit.platform.engine; diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java index 9732fda836c1..96e8fbcad24f 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -25,6 +25,7 @@ import java.util.stream.Stream; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.CollectionUtils; @@ -276,7 +277,8 @@ private static void withRequestLevelStore(Consumer newStore(NamespacedHierarchicalStore parentStore) { + private static NamespacedHierarchicalStore newStore( + @Nullable NamespacedHierarchicalStore parentStore) { return new NamespacedHierarchicalStore<>(parentStore, closeAutoCloseables()); } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java index 9d6187e5ccc1..4085b126483f 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java @@ -19,6 +19,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; @@ -93,7 +94,7 @@ public static Event dynamicTestRegistered(TestDescriptor testDescriptor) { * @return the newly created {@code Event} * @see EventType#SKIPPED */ - public static Event executionSkipped(TestDescriptor testDescriptor, String reason) { + public static Event executionSkipped(TestDescriptor testDescriptor, @Nullable String reason) { return new Event(EventType.SKIPPED, testDescriptor, reason); } @@ -170,6 +171,8 @@ public static Predicate byTestDescriptor(Predicate byTestDescriptor(Predicate events, SoftAssertions softly, Co } } - private static Event findEvent(List events, SoftAssertions softly, Condition condition) { + private static @Nullable Event findEvent(List events, SoftAssertions softly, + Condition condition) { // @formatter:off Optional matchedEvent = events.stream() .filter(condition::matches) diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java index 092c10fe1440..fa579b75cafa 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestExecutionResult; @@ -38,7 +39,7 @@ public class TerminationInfo { * @return the created {@code TerminationInfo}; never {@code null} * @see #executed(TestExecutionResult) */ - public static TerminationInfo skipped(String reason) { + public static TerminationInfo skipped(@Nullable String reason) { return new TerminationInfo(true, reason, null); } @@ -58,10 +59,15 @@ public static TerminationInfo executed(TestExecutionResult testExecutionResult) // ------------------------------------------------------------------------- private final boolean skipped; + + @Nullable private final String skipReason; + + @Nullable private final TestExecutionResult testExecutionResult; - private TerminationInfo(boolean skipped, String skipReason, TestExecutionResult testExecutionResult) { + private TerminationInfo(boolean skipped, @Nullable String skipReason, + @Nullable TestExecutionResult testExecutionResult) { boolean executed = (testExecutionResult != null); Preconditions.condition((skipped ^ executed), "TerminationInfo must represent either a skipped execution or a TestExecutionResult but not both"); @@ -109,7 +115,7 @@ public boolean executed() { * @throws UnsupportedOperationException if this {@code TerminationInfo} * does not represent a skipped execution */ - public String getSkipReason() throws UnsupportedOperationException { + public @Nullable String getSkipReason() throws UnsupportedOperationException { if (skipped()) { return this.skipReason; } @@ -125,7 +131,7 @@ public String getSkipReason() throws UnsupportedOperationException { * does not represent a completed execution */ public TestExecutionResult getExecutionResult() throws UnsupportedOperationException { - if (executed()) { + if (this.testExecutionResult != null) { return this.testExecutionResult; } // else diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java index c50fec3e29c1..808c8a16ef53 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/package-info.java @@ -3,4 +3,7 @@ * running on the JUnit Platform. */ +@NullMarked package org.junit.platform.testkit.engine; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java index 7a10b625264b..3a5c5b1062d3 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/package-info.java @@ -2,4 +2,7 @@ * Test Kit for the JUnit Platform. */ +@NullMarked package org.junit.platform.testkit; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-vintage-engine/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts index af2455f46c31..9eef3cf490fc 100644 --- a/junit-vintage-engine/junit-vintage-engine.gradle.kts +++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") id("junitbuild.junit4-compatibility") id("junitbuild.testing-conventions") `java-test-fixtures` @@ -14,6 +15,7 @@ dependencies { api(libs.junit4) compileOnlyApi(libs.apiguardian) + compileOnly(libs.jspecify) testFixturesApi(platform(libs.groovy2.bom)) testFixturesApi(libs.spock1) @@ -43,10 +45,13 @@ tasks { bundle { val junit4Min = libs.versions.junit4Min.get() val version = project.version + val importAPIGuardian: String by extra + val importJSpecify: String by extra bnd(""" # Import JUnit4 packages with a version Import-Package: \ - ${extra["importAPIGuardian"]},\ + ${importAPIGuardian},\ + ${importJSpecify},\ junit.runner;version="[${junit4Min},5)",\ org.junit;version="[${junit4Min},5)",\ org.junit.experimental.categories;version="[${junit4Min},5)",\ diff --git a/junit-vintage-engine/src/main/java/module-info.java b/junit-vintage-engine/src/main/java/module-info.java index 6880a39df91d..9c982e4cfa62 100644 --- a/junit-vintage-engine/src/main/java/module-info.java +++ b/junit-vintage-engine/src/main/java/module-info.java @@ -17,8 +17,11 @@ * runs JUnit 3 and 4 based tests on the platform. */ module org.junit.vintage.engine { - requires junit; // 4 + requires static org.apiguardian.api; + requires static org.jspecify; + + requires junit; // 4 requires org.junit.platform.engine; provides org.junit.platform.engine.TestEngine diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java index a55ffd3c572f..915e3c18d713 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java @@ -11,6 +11,7 @@ package org.junit.vintage.engine.descriptor; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.runner.Description; @API(status = API.Status.INTERNAL, since = "5.8") @@ -19,7 +20,7 @@ public class DescriptionUtils { private DescriptionUtils() { } - public static String getMethodName(Description description) { + public static @Nullable String getMethodName(Description description) { String displayName = description.getDisplayName(); int i = displayName.indexOf('('); if (i >= 0) { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java index dfe014468d7f..d5d55a8176dc 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java @@ -26,6 +26,7 @@ import java.util.function.Consumer; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; @@ -54,6 +55,8 @@ public class RunnerTestDescriptor extends VintageTestDescriptor { private Runner runner; private final boolean ignored; private boolean wasFiltered; + + @Nullable private List filters = new ArrayList<>(); public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner, boolean ignored) { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java index 2037f7aeb8d8..78fd2afbb585 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.LruCache; import org.junit.platform.engine.TestSource; @@ -44,7 +45,7 @@ public class TestSourceProvider { private final Map testSourceCache = new ConcurrentHashMap<>(); private final Map, List> methodsCache = synchronizedMap(new LruCache<>(31)); - public TestSource findTestSource(Description description) { + public @Nullable TestSource findTestSource(Description description) { TestSource testSource = testSourceCache.computeIfAbsent(description, this::computeTestSource); return testSource == NULL_SOURCE ? null : testSource; } @@ -72,7 +73,7 @@ private String sanitizeMethodName(String methodName) { return methodName; } - private Method findMethod(Class testClass, String methodName) { + private @Nullable Method findMethod(Class testClass, String methodName) { List methods = methodsCache.computeIfAbsent(testClass, clazz -> findMethods(clazz, m -> true, TOP_DOWN)).stream() // .filter(where(Method::getName, isEqual(methodName))) // 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 47319da6e3b6..d9ad9ea0ac73 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 @@ -23,6 +23,7 @@ import java.util.Set; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.experimental.categories.Category; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestDescriptor; @@ -45,15 +46,16 @@ public class VintageTestDescriptor extends AbstractTestDescriptor { protected Description description; - public VintageTestDescriptor(UniqueId uniqueId, Description description, TestSource source) { + public VintageTestDescriptor(UniqueId uniqueId, Description description, @Nullable TestSource source) { this(uniqueId, description, generateDisplayName(description), source); } - VintageTestDescriptor(UniqueId uniqueId, Description description, String displayName, TestSource source) { + VintageTestDescriptor(UniqueId uniqueId, Description description, String displayName, @Nullable TestSource source) { super(uniqueId, displayName, source); this.description = description; } + @SuppressWarnings("NullAway") private static String generateDisplayName(Description description) { String methodName = DescriptionUtils.getMethodName(description); return isNotBlank(methodName) ? methodName : description.getDisplayName(); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java index bef751cbdc9f..7743a7448580 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/package-info.java @@ -2,4 +2,7 @@ * Test descriptors used within the JUnit Vintage test engine. */ +@NullMarked package org.junit.vintage.engine.descriptor; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java index e8a005591a40..5a2741d42ecc 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java @@ -56,7 +56,7 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { String testClassName = lastSegment.getValue(); if (classFilter.match(testClassName)) { Class testClass = ReflectionSupport.tryToLoadClass(testClassName)// - .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); + .getNonNullOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); return resolveTestClassThatPassedNameFilter(testClass, context); } } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java index db80b987d897..c858e3c06b5e 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java @@ -13,6 +13,7 @@ import java.lang.reflect.Method; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; import org.junit.internal.builders.AllDefaultPossibilitiesBuilder; import org.junit.internal.builders.AnnotatedBuilder; @@ -109,7 +110,7 @@ private static class DefensiveAnnotatedBuilder extends AnnotatedBuilder { } @Override - public Runner buildRunner(Class runnerClass, Class testClass) throws Exception { + public @Nullable Runner buildRunner(Class runnerClass, Class testClass) throws Exception { // Referenced by name because it might not be available at runtime. if ("org.junit.platform.runner.JUnitPlatform".equals(runnerClass.getName())) { logger.warn(() -> "Ignoring test class using JUnitPlatform runner: " + testClass.getName()); @@ -128,7 +129,7 @@ private static class DefensiveJUnit4Builder extends JUnit4Builder { private static final Predicate isPotentialJUnit4TestMethod = new IsPotentialJUnit4TestMethod(); @Override - public Runner runnerForClass(Class testClass) throws Throwable { + public @Nullable Runner runnerForClass(Class testClass) throws Throwable { if (containsTestMethods(testClass)) { return super.runnerForClass(testClass); } @@ -147,7 +148,7 @@ private boolean containsTestMethods(Class testClass) { */ private static class NullIgnoredBuilder extends IgnoredBuilder { @Override - public Runner runnerForClass(Class testClass) { + public @Nullable Runner runnerForClass(Class testClass) { // don't ignore entire test classes just yet return null; } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java index 4f86e78b6579..1e14ea908c7c 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java @@ -107,7 +107,7 @@ public boolean shouldRun(Description description) { private boolean isParameterizedMethod(Description description) { String methodName = DescriptionUtils.getMethodName(description); - return methodName.startsWith(desiredMethodName + "["); + return methodName != null && methodName.startsWith(desiredMethodName + "["); } @Override diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java index a6d15af76b80..427bcae80943 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.runner.Description; @@ -33,7 +34,10 @@ class UniqueIdFilter extends Filter { private final RunnerTestDescriptor runnerTestDescriptor; private final UniqueId uniqueId; + @Nullable private Deque path; + + @Nullable private Set descendants; UniqueIdFilter(RunnerTestDescriptor runnerTestDescriptor, UniqueId uniqueId) { @@ -74,6 +78,7 @@ private Set determineDescendants(Optional } @Override + @SuppressWarnings({ "NullAway", "DataFlowIssue" }) public boolean shouldRun(Description description) { ensureInitialized(); return path.contains(description) || descendants.contains(description); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java index a22ae3939187..4e99c954b753 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/package-info.java @@ -2,4 +2,7 @@ * Internal classes for test discovery within the JUnit Vintage test engine. */ +@NullMarked package org.junit.vintage.engine.discovery; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java index 8aec956c3af3..6a0b4756f7fd 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -15,6 +15,7 @@ import java.util.Optional; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.Ignore; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; @@ -192,7 +193,7 @@ private Optional determineReasonForIgnoredTest(TestDescriptor testDescri .flatMap(testClass -> getReason(testClass.getAnnotation(Ignore.class))); } - private static Optional getReason(Ignore annotation) { + private static Optional getReason(@Nullable Ignore annotation) { return Optional.ofNullable(annotation).map(Ignore::value); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java index dd9ba6d31e21..8ddde9d15c66 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java @@ -11,6 +11,7 @@ package org.junit.vintage.engine.execution; import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; @@ -123,7 +124,7 @@ private Optional lookupUnambiguouslyOrApplyFallback(Descr void markSkipped(TestDescriptor testDescriptor) { skippedDescriptors.add(testDescriptor); if (testDescriptor instanceof VintageTestDescriptor vintageDescriptor) { - descriptionToDescriptors.get(vintageDescriptor.getDescription()).incrementSkippedOrStarted(); + getVintageDescriptors(vintageDescriptor).incrementSkippedOrStarted(); } } @@ -140,10 +141,15 @@ void markStarted(TestDescriptor testDescriptor, EventType eventType) { startedDescriptors.add(testDescriptor); if (testDescriptor instanceof VintageTestDescriptor vintageDescriptor) { inProgressDescriptorsByStartingThread.get().addLast(vintageDescriptor); - descriptionToDescriptors.get(vintageDescriptor.getDescription()).incrementSkippedOrStarted(); + getVintageDescriptors(vintageDescriptor).incrementSkippedOrStarted(); } } + private VintageDescriptors getVintageDescriptors(VintageTestDescriptor vintageDescriptor) { + return requireNonNull(descriptionToDescriptors.get(vintageDescriptor.getDescription()), + () -> "No descriptors for " + vintageDescriptor); + } + boolean isNotStarted(TestDescriptor testDescriptor) { return !startedDescriptors.contains(testDescriptor); } @@ -191,7 +197,7 @@ TestExecutionResult getStoredResultOrSuccessful(TestDescriptor testDescriptor) { List failures = testExecutionResults .stream() .map(TestExecutionResult::getThrowable) - .map(Optional::get) + .map(Optional::orElseThrow) .collect(toList()); // @formatter:on MultipleFailuresError multipleFailuresError = new MultipleFailuresError("", failures); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java index 0ca61c6655fe..64bc1dc03b1a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/VintageExecutor.java @@ -10,6 +10,7 @@ package org.junit.vintage.engine.execution; +import static java.util.Objects.requireNonNullElse; import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.vintage.engine.Constants.PARALLEL_CLASS_EXECUTION; @@ -165,7 +166,7 @@ private boolean executeClassesInParallel(List runnerTestDe wasInterrupted = true; } catch (ExecutionException e) { - throw ExceptionUtils.throwAsUncheckedException(e.getCause()); + throw ExceptionUtils.throwAsUncheckedException(requireNonNullElse(e.getCause(), e)); } finally { shutdownExecutorService(executorService); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java index 43e48e1d4ad6..3749427c181f 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/package-info.java @@ -2,4 +2,7 @@ * Internal classes for test execution within the JUnit Vintage test engine. */ +@NullMarked package org.junit.vintage.engine.execution; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java index af7d60d30f7e..05e48ea8ece3 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/package-info.java @@ -2,4 +2,7 @@ * Core package for the JUnit Vintage test engine. */ +@NullMarked package org.junit.vintage.engine; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java index bb5a291af76f..35401fcf225c 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/package-info.java @@ -3,4 +3,7 @@ * Vintage test engine. */ +@NullMarked package org.junit.vintage.engine.support; + +import org.jspecify.annotations.NullMarked; diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java index 07dab7575284..b8a91e6d2a2d 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java @@ -10,10 +10,6 @@ package org.junit.vintage.engine.samples.junit4; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Retention; - import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,8 +28,3 @@ public void test() { } } - -@Retention(RUNTIME) -@interface Label { - String value(); -} diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Label.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Label.java new file mode 100644 index 000000000000..5450edc8bb8c --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Label.java @@ -0,0 +1,20 @@ +/* + * 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.vintage.engine.samples.junit4; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; + +@Retention(RUNTIME) +@interface Label { + String value(); +} diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/package-info.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/package-info.java new file mode 100644 index 000000000000..678ca9e2e64c --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/package-info.java @@ -0,0 +1,5 @@ + +@NullUnmarked +package org.junit.vintage.engine.samples.junit4; + +import org.jspecify.annotations.NullUnmarked; diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts index dadeecfea33b..8c4526753061 100644 --- a/jupiter-tests/jupiter-tests.gradle.kts +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -2,6 +2,7 @@ import org.gradle.api.tasks.PathSensitivity.RELATIVE plugins { id("junitbuild.code-generator") + id("junitbuild.java-nullability-conventions") id("junitbuild.kotlin-library-conventions") id("junitbuild.junit4-compatibility") id("junitbuild.testing-conventions") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java index dac3a628fb17..f87e109281cf 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java @@ -38,31 +38,37 @@ */ class AssertAllAssertionsTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void assertAllWithNullExecutableArray() { assertPrecondition("executables array must not be null or empty", () -> assertAll((Executable[]) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void assertAllWithNullExecutableCollection() { assertPrecondition("executables collection must not be null", () -> assertAll((Collection) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void assertAllWithNullExecutableStream() { assertPrecondition("executables stream must not be null", () -> assertAll((Stream) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void assertAllWithNullInExecutableArray() { assertPrecondition("individual executables must not be null", () -> assertAll((Executable) null)); } + @SuppressWarnings({ "NullAway" }) @Test void assertAllWithNullInExecutableCollection() { assertPrecondition("individual executables must not be null", () -> assertAll(asList((Executable) null))); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void assertAllWithNullInExecutableStream() { assertPrecondition("individual executables must not be null", () -> assertAll(Stream.of((Executable) null))); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java index ff666a7ef0f5..1e3fa189b858 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java @@ -17,6 +17,7 @@ import java.io.IOException; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.opentest4j.AssertionFailedError; @@ -91,7 +92,7 @@ private void assertInstanceOfSucceeds(Class expectedType, Object actualVa assertSame(res, actualValue); } - private void assertInstanceOfFails(Class expectedType, Object actualValue, String unexpectedSort) { + private void assertInstanceOfFails(Class expectedType, @Nullable Object actualValue, String unexpectedSort) { String valueType = actualValue == null ? "null" : actualValue.getClass().getCanonicalName(); String expectedMessage = "Unexpected %s, expected: <%s> but was: <%s>".formatted(unexpectedSort, expectedType.getCanonicalName(), valueType); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java index 273b75d11276..a1d9720c7cda 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java @@ -95,7 +95,7 @@ void assertLinesMatchUsingFastForwardMarkerWithLimit3() { } @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked", "rawtypes", "DataFlowIssue", "NullAway" }) void assertLinesMatchWithNullFails() { assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(null, (List) null)); assertThrows(PreconditionViolationException.class, () -> assertLinesMatch(null, Collections.emptyList())); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java index e960d250a993..7ec74ae92ee3 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java @@ -18,6 +18,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; /** @@ -63,7 +65,8 @@ void assertNullWithNonNullObjectWithNullStringReturnedFromToStringAndMessageSupp } @SuppressWarnings("unused") - private void assertNullWithNonNullObjectWithNullStringReturnedFromToString(Supplier messageSupplier) { + private void assertNullWithNonNullObjectWithNullStringReturnedFromToString( + @Nullable Supplier messageSupplier) { String actual = "null"; try { if (messageSupplier == null) { @@ -94,7 +97,8 @@ void assertNullWithNonNullObjectWithNullReferenceReturnedFromToStringAndMessageS } @SuppressWarnings("unused") - private void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString(Supplier messageSupplier) { + private void assertNullWithNonNullObjectWithNullReferenceReturnedFromToString( + @Nullable Supplier messageSupplier) { Object actual = new NullToString(); try { if (messageSupplier == null) { @@ -141,6 +145,7 @@ void assertNullWithNonNullObjectAndMessageSupplier() { } } + @NullUnmarked private static class NullToString { @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java index c1f5a003cbc4..9f9e3edf4131 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java @@ -47,6 +47,7 @@ void assertThrowsWithMethodReferenceForNonVoidReturnType() { future.run(); ExecutionException exception = assertThrows(ExecutionException.class, future::get); + assertNotNull(exception.getCause()); assertEquals("boom", exception.getCause().getMessage()); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java index d167f4f08e43..ea683191e03e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java @@ -87,6 +87,7 @@ void assertThrowsWithMethodReferenceForNonVoidReturnType() { future.run(); ExecutionException exception = assertThrowsExactly(ExecutionException.class, future::get); + assertNotNull(exception.getCause()); assertEquals("boom", exception.getCause().getMessage()); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java index 303bbb3124fe..b7fb03847676 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java @@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.condition.OS.WINDOWS; @@ -28,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.util.ExceptionUtils; import org.opentest4j.AssertionFailedError; @@ -81,7 +81,7 @@ void assertTimeoutPreemptivelyForExecutableThatCompletesAfterTheTimeout() { () -> assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, this::waitForInterrupt)); assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test @@ -91,7 +91,7 @@ void assertTimeoutPreemptivelyWithMessageForExecutableThatCompletesAfterTheTimeo assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test @@ -102,7 +102,7 @@ void assertTimeoutPreemptivelyWithMessageSupplierForExecutableThatCompletesAfter assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test @@ -156,7 +156,7 @@ void assertTimeoutPreemptivelyForSupplierThatCompletesAfterTheTimeout() { assertMessageEquals(error, "execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test @@ -171,7 +171,7 @@ void assertTimeoutPreemptivelyWithMessageForSupplierThatCompletesAfterTheTimeout assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test @@ -186,15 +186,16 @@ void assertTimeoutPreemptivelyWithMessageSupplierForSupplierThatCompletesAfterTh assertMessageEquals(error, "Tempus Fugit ==> execution timed out after " + PREEMPTIVE_TIMEOUT.toMillis() + " ms"); assertMessageStartsWith(error.getCause(), "Execution timed out in "); - assertStackTraceContains(error.getCause().getStackTrace(), "CountDownLatch", "await"); + assertStackTraceContains(error.getCause(), "CountDownLatch", "await"); } @Test void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { AtomicReference threadName = new AtomicReference<>(""); assertTimeoutPreemptively(ofMillis(1000), () -> threadName.set(Thread.currentThread().getName())); - assertTrue(threadName.get().startsWith("junit-timeout-thread-"), - "Thread name does not match the expected prefix"); + assertThat(threadName.get()) // + .withFailMessage("Thread name does not match the expected prefix") // + .startsWith("junit-timeout-thread-"); } @Test @@ -244,8 +245,9 @@ private void waitForInterrupt() { /** * Assert the given stack trace elements contain an element with the given class name and method name. */ - private static void assertStackTraceContains(StackTraceElement[] stackTrace, String className, String methodName) { - assertThat(stackTrace).anySatisfy(element -> { + private static void assertStackTraceContains(@Nullable Throwable throwable, String className, String methodName) { + assertThat(throwable).isNotNull(); + assertThat(throwable.getStackTrace()).anySatisfy(element -> { assertThat(element.getClassName()).endsWith(className); assertThat(element.getMethodName()).isEqualTo(methodName); }); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java index d961acd53496..a5bb7c0886a0 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java @@ -13,6 +13,8 @@ import java.io.Serializable; import java.util.Objects; +import org.apache.groovy.parser.antlr4.util.StringUtils; +import org.jspecify.annotations.Nullable; import org.opentest4j.AssertionFailedError; import org.opentest4j.ValueWrapper; @@ -27,7 +29,7 @@ static void expectAssertionFailedError() { } static void assertEmptyMessage(Throwable ex) throws AssertionError { - if (!ex.getMessage().isEmpty()) { + if (!StringUtils.isEmpty(ex.getMessage())) { throw new AssertionError("Exception message should be empty, but was [" + ex.getMessage() + "]."); } } @@ -39,35 +41,41 @@ static void assertMessageEquals(Throwable ex, String msg) throws AssertionError } static void assertMessageMatches(Throwable ex, String regex) throws AssertionError { - if (!ex.getMessage().matches(regex)) { + if (ex.getMessage() == null || !ex.getMessage().matches(regex)) { throw new AssertionError("Exception message should match regular expression [" + regex + "], but was [" + ex.getMessage() + "]."); } } - static void assertMessageStartsWith(Throwable ex, String msg) throws AssertionError { - if (!ex.getMessage().startsWith(msg)) { + static void assertMessageStartsWith(@Nullable Throwable ex, String msg) throws AssertionError { + if (ex == null) { + throw new AssertionError("Cause should not have been null"); + } + if (ex.getMessage() == null || !ex.getMessage().startsWith(msg)) { throw new AssertionError( "Exception message should start with [" + msg + "], but was [" + ex.getMessage() + "]."); } } static void assertMessageEndsWith(Throwable ex, String msg) throws AssertionError { - if (!ex.getMessage().endsWith(msg)) { + if (ex.getMessage() == null || !ex.getMessage().endsWith(msg)) { throw new AssertionError( "Exception message should end with [" + msg + "], but was [" + ex.getMessage() + "]."); } } - static void assertMessageContains(Throwable ex, String msg) throws AssertionError { - if (!ex.getMessage().contains(msg)) { + static void assertMessageContains(@Nullable Throwable ex, String msg) throws AssertionError { + if (ex == null) { + throw new AssertionError("Cause should not have been null"); + } + if (ex.getMessage() == null || !ex.getMessage().contains(msg)) { throw new AssertionError( "Exception message should contain [" + msg + "], but was [" + ex.getMessage() + "]."); } } - static void assertExpectedAndActualValues(AssertionFailedError ex, Object expected, Object actual) - throws AssertionError { + static void assertExpectedAndActualValues(AssertionFailedError ex, @Nullable Object expected, + @Nullable Object actual) throws AssertionError { if (!wrapsEqualValue(ex.getExpected(), expected)) { throw new AssertionError("Expected value in AssertionFailedError should equal [" + ValueWrapper.create(expected) + "], but was [" + ex.getExpected() + "]."); @@ -78,7 +86,7 @@ static void assertExpectedAndActualValues(AssertionFailedError ex, Object expect } } - static boolean wrapsEqualValue(ValueWrapper wrapper, Object value) { + static boolean wrapsEqualValue(ValueWrapper wrapper, @Nullable Object value) { if (value == null || value instanceof Serializable) { return Objects.equals(value, wrapper.getValue()); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java index 7aa35c56b634..499704bf6604 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.opentest4j.TestAbortedException; @@ -216,11 +217,11 @@ void abortWithStringSupplier() { // ------------------------------------------------------------------- - private static void assertAssumptionFailure(String msg, Executable executable) { + private static void assertAssumptionFailure(@Nullable String msg, Executable executable) { assertTestAbortedException(msg == null ? "Assumption failed" : "Assumption failed: " + msg, executable); } - private static void assertTestAbortedException(String expectedMessage, Executable executable) { + private static void assertTestAbortedException(@Nullable String expectedMessage, Executable executable) { try { executable.execute(); expectTestAbortedException(); @@ -235,7 +236,7 @@ private static void expectTestAbortedException() { throw new AssertionError("Should have thrown a " + TestAbortedException.class.getName()); } - private static void assertMessageEquals(Throwable t, String expectedMessage) throws AssertionError { + private static void assertMessageEquals(Throwable t, @Nullable String expectedMessage) throws AssertionError { if (!Objects.equals(expectedMessage, t.getMessage())) { throw new AssertionError("Message in TestAbortedException should be [" + expectedMessage + "], but was [" + t.getMessage() + "]."); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java index 323470e59556..bf356db5d3ca 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -397,8 +397,6 @@ static class UnderscoreStyleInheritedFromSuperClassTestCase extends UnderscoreSt @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) static class StackTestCase { - Stack stack; - @Test void is_instantiated_using_its_noarg_constructor() { new Stack<>(); @@ -407,6 +405,8 @@ void is_instantiated_using_its_noarg_constructor() { @Nested class A_new_stack { + Stack stack; + @BeforeEach void createNewStack() { stack = new Stack<>(); @@ -464,8 +464,6 @@ void peek_returns_that_element_without_removing_it_from_the_stack() { @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeGeneratorTestCase { - Stack stack; - @Test void is_instantiated_with_its_constructor() { new Stack<>(); @@ -474,6 +472,8 @@ void is_instantiated_with_its_constructor() { @Nested class when_new { + Stack stack; + @BeforeEach void create_with_new_stack() { stack = new Stack<>(); @@ -509,8 +509,6 @@ void is_no_longer_empty() { @IndicativeSentencesGeneration(separator = " >> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeGeneratorWithCustomSeparatorTestCase { - Stack stack; - @Test void is_instantiated_with_its_constructor() { new Stack<>(); @@ -519,6 +517,8 @@ void is_instantiated_with_its_constructor() { @Nested class when_new { + Stack stack; + @BeforeEach void create_with_new_stack() { stack = new Stack<>(); @@ -554,8 +554,6 @@ void is_no_longer_empty() { @IndicativeSentencesGeneration static class IndicativeGeneratorWithCustomSentenceFragmentsTestCase { - Stack stack; - @SentenceFragment("is instantiated with its constructor") @Test void instantiateViaConstructor() { @@ -566,6 +564,8 @@ void instantiateViaConstructor() { @Nested class NewStackTestCase { + Stack stack; + @BeforeEach void createNewStack() { stack = new Stack<>(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java index 073678ad4088..5f33ded5be82 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.platform.commons.PreconditionViolationException; @@ -41,8 +42,9 @@ class DynamicTestTests { private static final Executable nix = () -> { }; - private final List assertedValues = new ArrayList<>(); + private final List<@Nullable String> assertedValues = new ArrayList<>(); + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamFromStreamPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -57,6 +59,7 @@ void streamFromStreamPreconditions() { () -> DynamicTest.stream(Stream.empty(), displayNameGenerator, null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamFromIteratorPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -71,6 +74,7 @@ void streamFromIteratorPreconditions() { () -> DynamicTest.stream(emptyIterator(), displayNameGenerator, null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamFromStreamWithNamesPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -81,6 +85,7 @@ void streamFromStreamWithNamesPreconditions() { assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(Stream.empty(), null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamFromIteratorWithNamesPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -91,12 +96,14 @@ void streamFromIteratorWithNamesPreconditions() { assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(emptyIterator(), null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamFromStreamWithNamedExecutablesPreconditions() { assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream((Stream) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamFromIteratorWithNamedExecutablesPreconditions() { assertThrows(PreconditionViolationException.class, @@ -170,7 +177,7 @@ private void assertStream(Stream stream) throws Throwable { assertThat(assertedValues).containsExactly("foo", "bar"); } - private void throwingConsumer(String str) throws Throwable { + private void throwingConsumer(@Nullable String str) throws Throwable { if ("baz".equals(str)) { throw new Throwable("Baz!"); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java index d549bbbed364..252a7974ba95 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java @@ -73,6 +73,7 @@ void failWithNullString() { } } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void failWithNullMessageSupplier() { try { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java index 0dcb9c312037..ae363fe49c04 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java @@ -15,13 +15,16 @@ import java.util.List; import java.util.Set; +import org.jspecify.annotations.Nullable; + final class IterableFactory { - static List listOf(Object... objects) { + @SuppressWarnings("NullableProblems") + static List listOf(@Nullable Object... objects) { return Arrays.asList(objects); } - static Set setOf(Object... objects) { + static Set setOf(@Nullable Object... objects) { return new LinkedHashSet<>(listOf(objects)); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java index bff8050468de..4b213419aad0 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethod; @@ -27,6 +28,7 @@ import java.util.Optional; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -53,6 +55,7 @@ abstract class AbstractExecutionConditionTests { private final ExtensionContext context = mock(); + @Nullable private ConditionEvaluationResult result; @BeforeAll @@ -84,18 +87,22 @@ protected void evaluateCondition() { } protected void assertEnabled() { + assertNotNull(this.result); assertFalse(this.result.isDisabled(), "Should be enabled"); } protected void assertDisabled() { + assertNotNull(this.result); assertTrue(this.result.isDisabled(), "Should be disabled"); } protected void assertReasonContains(String text) { + assertNotNull(this.result); assertThat(this.result.getReason()).hasValueSatisfying(reason -> assertThat(reason).contains(text)); } protected void assertCustomDisabledReasonIs(String text) { + assertNotNull(this.result); if (this.result.isDisabled()) { assertThat(this.result.getReason()).hasValueSatisfying( reason -> assertThat(reason).contains(" ==> " + text)); @@ -103,11 +110,11 @@ protected void assertCustomDisabledReasonIs(String text) { } private Optional method(TestInfo testInfo) { - return method(getTestClass(), testInfo.getTestMethod().get().getName()); + return method(getTestClass(), testInfo.getTestMethod().orElseThrow().getName()); } private Optional method(Class testClass, String methodName) { - return Optional.of(findMethod(testClass, methodName).get()); + return Optional.of(findMethod(testClass, methodName).orElseThrow()); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java index c9c7434a5bea..921f5d173e4e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY1; import static org.junit.jupiter.api.condition.DisabledIfEnvironmentVariableIntegrationTests.KEY2; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.PreconditionViolationException; @@ -36,7 +37,7 @@ class DisabledIfEnvironmentVariableConditionTests extends AbstractExecutionCondi private ExecutionCondition condition = new DisabledIfEnvironmentVariableCondition() { @Override - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) ? ENIGMA : null; } }; @@ -99,7 +100,7 @@ void disabledBecauseEnvironmentVariableForComposedAnnotationMatchesExactly() { this.condition = new DisabledIfEnvironmentVariableCondition() { @Override - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; } }; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java index db0116e38695..7410b433c1eb 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY1; import static org.junit.jupiter.api.condition.EnabledIfEnvironmentVariableIntegrationTests.KEY2; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.PreconditionViolationException; @@ -37,7 +38,7 @@ class EnabledIfEnvironmentVariableConditionTests extends AbstractExecutionCondit private ExecutionCondition condition = new EnabledIfEnvironmentVariableCondition() { @Override - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) ? ENIGMA : null; } }; @@ -100,7 +101,7 @@ void enabledBecauseBothEnvironmentVariablesMatchExactly() { this.condition = new EnabledIfEnvironmentVariableCondition() { @Override - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) || KEY2.equals(name) ? ENIGMA : null; } }; @@ -141,7 +142,7 @@ void disabledBecauseEnvironmentVariableForComposedAnnotationDoesNotMatch() { this.condition = new EnabledIfEnvironmentVariableCondition() { @Override - protected String getEnvironmentVariable(String name) { + protected @Nullable String getEnvironmentVariable(String name) { return KEY1.equals(name) ? ENIGMA : KEY2.equals(name) ? BOGUS : null; } }; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java index ada59dbdda55..ee8d1dba0729 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java @@ -30,6 +30,7 @@ * @since 5.0 * @see ExtensionComposabilityTests */ +@SuppressWarnings({ "DataFlowIssue", "NullAway" }) // @formatter:off public class KitchenSinkExtension implements diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java index b1d06036b6fc..7e1bea151e7c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java @@ -45,6 +45,7 @@ void parseWithInvalidMediaType() { assertEquals("Invalid media type: 'invalid'", exception.getMessage()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void parseWithNullMediaType() { var exception = assertThrows(PreconditionViolationException.class, () -> MediaType.parse(null)); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java index fb8c6b0ed0b2..a8b06d75f34b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java @@ -56,20 +56,20 @@ void missingTypeTypeBasedParameterResolver() { @Test void supportsParameterForBasicTypes() { Parameter parameter1 = findParameterOfMethod("methodWithBasicTypeParameter", String.class); - assertTrue(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); - assertTrue(subClassedBasicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); + assertTrue(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), mock())); + assertTrue(subClassedBasicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), mock())); Parameter parameter2 = findParameterOfMethod("methodWithObjectParameter", Object.class); - assertFalse(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter2), null)); + assertFalse(basicTypeBasedParameterResolver.supportsParameter(parameterContext(parameter2), mock())); } @Test void supportsParameterForParameterizedTypes() { Parameter parameter1 = findParameterOfMethod("methodWithParameterizedTypeParameter", Map.class); - assertTrue(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), null)); + assertTrue(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter1), mock())); Parameter parameter3 = findParameterOfMethod("methodWithAnotherParameterizedTypeParameter", Map.class); - assertFalse(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter3), null)); + assertFalse(parametrizedTypeBasedParameterResolver.supportsParameter(parameterContext(parameter3), mock())); } @Test @@ -98,7 +98,7 @@ private static ExtensionContext extensionContext() { } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).get(); + Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).orElseThrow(); return method.getParameters()[0]; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java index f5bc7a17b77b..750c886f429c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -11,6 +11,7 @@ package org.junit.jupiter.api.parallel; import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -24,6 +25,7 @@ import java.util.Set; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -115,6 +117,7 @@ static class Provider implements ResourceLocksProvider { private static boolean isProvideForNestedClassCalled = false; private static boolean isProvideForNestedTestMethodCalled = false; + @Nullable private Class testClass; @Override @@ -128,7 +131,7 @@ public Set provideForClass(Class testClass) { @Override public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; - assertEquals(List.of(this.testClass), enclosingInstanceTypes); + assertEquals(List.of(requireNonNull(this.testClass)), enclosingInstanceTypes); assertEquals(ClassLevelProviderTestCase.NestedClass.class, testClass); return emptySet(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java index cd15da225011..c39bc60b5fb7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ClassTemplateInvocationTests.java @@ -50,6 +50,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -1168,8 +1169,8 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) - throws ParameterResolutionException { + public @Nullable Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getStore(Namespace.GLOBAL).get("someResource"); } } @@ -1431,7 +1432,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getStore(PreparingClassTemplateInvocationContextProvider.NAMESPACE).get("resource"); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java index c67182cd7283..4857345b6cef 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java @@ -19,6 +19,7 @@ import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -41,7 +42,7 @@ void defaultExecutionModeIsReadFromConfigurationParameter() { assertUsesExpectedExecutionMode(CONCURRENT, CONCURRENT); } - private void assertUsesExpectedExecutionMode(ExecutionMode defaultExecutionMode, + private void assertUsesExpectedExecutionMode(@Nullable ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { var engineDescriptor = discoverTestsWithDefaultExecutionMode(TestCase.class, defaultExecutionMode); assertExecutionModeRecursively(engineDescriptor, expectedExecutionMode); @@ -63,7 +64,7 @@ void annotationOverridesDefaultExecutionModeToSameThreadForAllDescendants() { } private void assertUsesExpectedExecutionModeForTestClassAndItsDescendants(Class testClass, - ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { + @Nullable ExecutionMode defaultExecutionMode, ExecutionMode expectedExecutionMode) { var engineDescriptor = discoverTestsWithDefaultExecutionMode(testClass, defaultExecutionMode); engineDescriptor.getChildren().forEach(child -> assertExecutionModeRecursively(child, expectedExecutionMode)); } @@ -104,7 +105,7 @@ void methodsInNestedTestClassesWithInstancePerClassInHierarchyHaveExecutionModeS } private JupiterEngineDescriptor discoverTestsWithDefaultExecutionMode(Class testClass, - ExecutionMode executionMode) { + @Nullable ExecutionMode executionMode) { LauncherDiscoveryRequestBuilder request = request().selectors(selectClass(testClass)); if (executionMode != null) { request.configurationParameter(Constants.DEFAULT_PARALLEL_EXECUTION_MODE, executionMode.name()); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java index 3b3602e09fb8..64a75e51e58f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java @@ -15,11 +15,13 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +@NullUnmarked class NestedWithInheritanceTests extends SuperClass { static List lifecycleInvokingClassNames; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java index b73cee268861..360bf5ae3f99 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java @@ -10,11 +10,13 @@ package org.junit.jupiter.engine; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -22,6 +24,7 @@ class NestedWithSeparateInheritanceTests extends SuperClass1 { + @Nullable static List lifecycleInvokingClassNames; static String OUTER = NestedWithSeparateInheritanceTests.class.getSimpleName(); @@ -59,7 +62,7 @@ static void setup() { @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + requireNonNull(NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames).add(invokingClass); } } @@ -74,7 +77,7 @@ static void setup() { @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + requireNonNull(NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames).add(invokingClass); } } @@ -89,7 +92,7 @@ static void setup() { @BeforeEach public void beforeEach() { String invokingClass = this.getClass().getSimpleName(); - NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames.add(invokingClass); + requireNonNull(NestedWithSeparateInheritanceTests.lifecycleInvokingClassNames).add(invokingClass); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java index 7d69e5d8c932..7c9f0f9d035f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java @@ -115,6 +115,7 @@ void succeedingTest(TestReporter reporter) { file -> Files.writeString(file, "succeedingTest")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void invalidReportData(TestReporter reporter) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java index a360f0c8306e..185f903dc785 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java @@ -34,6 +34,7 @@ import java.util.Objects; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -96,6 +97,7 @@ void init() { afterEachCount = 0; } + @SuppressWarnings("NullAway") @Test void instancePerMethod() { Class testClass = InstancePerMethodTestCase.class; @@ -181,6 +183,7 @@ void instancePerClassWithInheritedLifecycleMode() { instanceCounts(entry(SubInstancePerClassTestCase.class, 1))); } + @SuppressWarnings("NullAway") private void instancePerClass(Class testClass, Map.Entry, Integer>[] instances) { int containers = 3; int tests = 3; @@ -248,6 +251,7 @@ private void instancePerClass(Class testClass, Map.Entry, Integer>[] assertThat(lifecyclesMap.get(testClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); } + @SuppressWarnings("NullAway") @Test void instancePerMethodWithNestedTestClass() { Class testClass = InstancePerMethodOuterTestCase.class; @@ -370,6 +374,7 @@ void instancePerMethodWithNestedTestClass() { assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_METHOD::equals); } + @SuppressWarnings("NullAway") @Test void instancePerClassWithNestedTestClass() { Class testClass = InstancePerClassOuterTestCase.class; @@ -490,6 +495,7 @@ void instancePerClassWithNestedTestClass() { assertThat(lifecyclesMap.get(nestedTestClass).stream()).allMatch(Lifecycle.PER_CLASS::equals); } + @SuppressWarnings("NullAway") @Test void instancePerMethodOnOuterTestClassWithInstancePerClassOnNestedTestClass() { Class testClass = MixedLifecyclesOuterTestCase.class; @@ -676,7 +682,7 @@ private static void incrementInstanceCount(Class testClass) { instanceCount.compute(testClass, (key, value) -> value == null ? 1 : value + 1); } - private static String executionConditionKey(Class testClass, String testMethod) { + private static String executionConditionKey(Class testClass, @Nullable String testMethod) { return concat(ExecutionCondition.class, testClass, testMethod); } @@ -708,7 +714,7 @@ private static String testTemplateKey(Class testClass, String testMethod) { return concat(TestTemplateInvocationContextProvider.class, testClass, testMethod); } - private static String concat(Class c1, Class c2, String str) { + private static String concat(Class c1, Class c2, @Nullable String str) { return concat(c1.getSimpleName(), c2.getSimpleName(), str); } @@ -716,7 +722,7 @@ private static String concat(Class c1, Class c2) { return concat(c1.getSimpleName(), c2.getSimpleName()); } - private static String concat(String... args) { + private static String concat(@Nullable String... args) { return join(".", args); } @@ -814,6 +820,7 @@ static void beforeAll(TestInfo testInfo) { beforeAllCount++; } + @SuppressWarnings("NullAway") @Test void outerTest() { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); @@ -839,18 +846,21 @@ void beforeEach() { beforeEachCount++; } + @SuppressWarnings("NullAway") @Test void test1(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } + @SuppressWarnings("NullAway") @Test void test2(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } + @SuppressWarnings("NullAway") @SingletonTest void singletonTest(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); @@ -879,6 +889,7 @@ static void beforeAll(TestInfo testInfo) { beforeAllCount++; } + @SuppressWarnings("NullAway") @Test void outerTest() { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); @@ -909,18 +920,21 @@ void beforeEach() { beforeEachCount++; } + @SuppressWarnings("NullAway") @Test void test1(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } + @SuppressWarnings("NullAway") @Test void test2(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } + @SuppressWarnings("NullAway") @SingletonTest void singletonTest(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); @@ -955,6 +969,7 @@ void beforeEach() { beforeEachCount++; } + @SuppressWarnings("NullAway") @Test void outerTest() { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); @@ -984,18 +999,21 @@ void beforeEach() { beforeEachCount++; } + @SuppressWarnings("NullAway") @Test void test1(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } + @SuppressWarnings("NullAway") @Test void test2(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); testsInvoked.add(testInfo.getTestMethod().get().getName()); } + @SuppressWarnings("NullAway") @SingletonTest void singletonTest(TestInfo testInfo) { assertSame(this, instanceMap.get(postProcessTestInstanceKey(getClass())).getInnermostInstance()); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java index 5cb6dba4dd82..9e5a7f9fe050 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java @@ -44,6 +44,7 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -499,6 +500,7 @@ void templateWithWrongParameterType(int parameter) { fail("never called: " + parameter); } + @Nullable String parameterInstanceVariable; @ExtendWith(StringParameterInjectingInvocationContextProvider.class) @@ -549,7 +551,7 @@ void template(String parameter) { static class TestTemplateTestClassWithBeforeAndAfterEach { - private static List lifecycleEvents = new ArrayList<>(); + private static final List lifecycleEvents = new ArrayList<>(); @BeforeAll static void beforeAll(TestInfo testInfo) { @@ -916,7 +918,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return extensionContext.getStore(PreparingTestTemplateInvocationContextProvider.NAMESPACE).get("resource"); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index ef7a98337044..af5fc8088059 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; @@ -42,6 +43,7 @@ class DefaultJupiterConfigurationTests { private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getDefaultTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, @@ -153,7 +155,7 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { assertThat(supplier.get()).isSameAs(TempDirFactory.Standard.INSTANCE); } - private void assertDefaultConfigParam(String configValue, Lifecycle expected) { + private void assertDefaultConfigParam(@Nullable String configValue, Lifecycle expected) { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue)); Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider(), diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java index ee7aa76cd0cd..b6252bb58ee2 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java @@ -227,6 +227,7 @@ class NestedTestCase { } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) static class NullDisplayNameGenerator implements DisplayNameGenerator { @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java index 1d41630c244f..f48979ec7dff 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Named; @@ -94,7 +95,7 @@ void setUp() { void fromJupiterEngineDescriptor() { var engineTestDescriptor = new JupiterEngineDescriptor(UniqueId.root("engine", "junit-jupiter"), configuration); - try (var engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, configuration, + try (var engineContext = new JupiterEngineExtensionContext(mock(), engineTestDescriptor, configuration, extensionRegistry, launcherStoreFacade)) { // @formatter:off assertAll("engineContext", @@ -125,8 +126,10 @@ void fromClassTestDescriptor() { nestedClassDescriptor.addChild(doublyNestedClassDescriptor); nestedClassDescriptor.addChild(methodTestDescriptor); - var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, PER_METHOD, - configuration, extensionRegistry, launcherStoreFacade, null); + var parentExtensionContext = mock(AbstractExtensionContext.class); + + var outerExtensionContext = new ClassExtensionContext(parentExtensionContext, mock(), outerClassDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); // @formatter:off assertAll("outerContext", @@ -138,15 +141,15 @@ void fromClassTestDescriptor() { () -> assertThrows(PreconditionViolationException.class, outerExtensionContext::getRequiredTestInstance), () -> assertThrows(PreconditionViolationException.class, outerExtensionContext::getRequiredTestMethod), () -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()), - () -> assertThat(outerExtensionContext.getParent()).isEmpty(), + () -> assertThat(outerExtensionContext.getParent()).containsSame(parentExtensionContext), () -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD), () -> assertThat(outerExtensionContext.getExtensions(PreInterruptCallback.class)).isEmpty(), () -> assertThat(outerExtensionContext.getEnclosingTestClasses()).isEmpty() ); // @formatter:on - var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor, - PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null); + var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, mock(), nestedClassDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); // @formatter:off assertAll("nestedContext", () -> assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext), @@ -155,8 +158,8 @@ void fromClassTestDescriptor() { ); // @formatter:on - var doublyNestedExtensionContext = new ClassExtensionContext(nestedExtensionContext, null, - doublyNestedClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null); + var doublyNestedExtensionContext = new ClassExtensionContext(nestedExtensionContext, mock(), + doublyNestedClassDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); // @formatter:off assertAll("doublyNestedContext", () -> assertThat(doublyNestedExtensionContext.getParent()).containsSame(nestedExtensionContext), @@ -165,7 +168,7 @@ void fromClassTestDescriptor() { ); // @formatter:on - var methodExtensionContext = new MethodExtensionContext(nestedExtensionContext, null, methodTestDescriptor, + var methodExtensionContext = new MethodExtensionContext(nestedExtensionContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); // @formatter:off assertAll("methodContext", @@ -179,8 +182,8 @@ void fromClassTestDescriptor() { @Test void ExtensionContext_With_ExtensionRegistry_getExtensions() { var classTestDescriptor = nestedClassDescriptor(); - try (var ctx = new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD, configuration, - extensionRegistry, launcherStoreFacade, null)) { + try (var ctx = new ClassExtensionContext(mock(AbstractExtensionContext.class), mock(), classTestDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock())) { Extension ext = mock(); when(extensionRegistry.getExtensions(Extension.class)).thenReturn(List.of(ext)); @@ -197,22 +200,26 @@ void tagsCanBeRetrievedInExtensionContext() { var methodTestDescriptor = methodDescriptor(); outerClassDescriptor.addChild(methodTestDescriptor); - var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, PER_METHOD, - configuration, extensionRegistry, launcherStoreFacade, null); + var rootExtensionContext = new JupiterEngineExtensionContext(mock(), mock(), configuration, extensionRegistry, + launcherStoreFacade); + assertThat(rootExtensionContext.getRoot()).isSameAs(rootExtensionContext); + + var outerExtensionContext = new ClassExtensionContext(rootExtensionContext, mock(), outerClassDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag"); - assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext); + assertThat(outerExtensionContext.getRoot()).isSameAs(rootExtensionContext); - var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor, - PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null); + var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, mock(), nestedClassDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag"); - assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext); + assertThat(nestedExtensionContext.getRoot()).isSameAs(rootExtensionContext); - var methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, methodTestDescriptor, + var methodExtensionContext = new MethodExtensionContext(outerExtensionContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClassTestCase())); assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag"); - assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext); + assertThat(methodExtensionContext.getRoot()).isSameAs(rootExtensionContext); } @Test @@ -226,11 +233,11 @@ void fromMethodTestDescriptor() { Object testInstance = new OuterClassTestCase(); var testMethod = methodTestDescriptor.getTestMethod(); - var engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, configuration, + var engineExtensionContext = new JupiterEngineExtensionContext(mock(), engineDescriptor, configuration, extensionRegistry, launcherStoreFacade); - var classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, classTestDescriptor, - PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null); - var methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, methodTestDescriptor, + var classExtensionContext = new ClassExtensionContext(engineExtensionContext, mock(), classTestDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); + var methodExtensionContext = new MethodExtensionContext(classExtensionContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance)); @@ -255,10 +262,11 @@ void fromMethodTestDescriptor() { @Test @SuppressWarnings("resource") void reportEntriesArePublishedToExecutionListener() { - var classTestDescriptor = outerClassDescriptor(null); + var classTestDescriptor = outerClassDescriptor(mock()); var engineExecutionListener = spy(EngineExecutionListener.class); - ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener, - classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, null); + ExtensionContext extensionContext = new ClassExtensionContext(mock(AbstractExtensionContext.class), + engineExecutionListener, classTestDescriptor, PER_METHOD, configuration, extensionRegistry, + launcherStoreFacade, mock()); var map1 = Collections.singletonMap("key", "value"); var map2 = Collections.singletonMap("other key", "other value"); @@ -383,8 +391,8 @@ private ExtensionContext createExtensionContextForFilePublishing(Path tempDir, EngineExecutionListener engineExecutionListener, ClassTestDescriptor classTestDescriptor) { when(configuration.getOutputDirectoryProvider()) // .thenReturn(hierarchicalOutputDirectoryProvider(tempDir)); - return new ClassExtensionContext(null, engineExecutionListener, classTestDescriptor, PER_METHOD, configuration, - extensionRegistry, launcherStoreFacade, null); + return new ClassExtensionContext(mock(AbstractExtensionContext.class), engineExecutionListener, + classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); } @Test @@ -392,9 +400,9 @@ private ExtensionContext createExtensionContextForFilePublishing(Path tempDir, void usingStore() { var methodTestDescriptor = methodDescriptor(); var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); - ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD, - configuration, extensionRegistry, launcherStoreFacade, null); - var childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, configuration, + ExtensionContext parentContext = new ClassExtensionContext(mock(AbstractExtensionContext.class), mock(), + classTestDescriptor, PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); + var childContext = new MethodExtensionContext(parentContext, mock(), methodTestDescriptor, configuration, extensionRegistry, launcherStoreFacade, new OpenTest4JAwareThrowableCollector()); childContext.setTestInstances(DefaultTestInstances.of(new OuterClassTestCase())); @@ -448,22 +456,22 @@ void configurationParameter(Function { var engineUniqueId = UniqueId.parse("[engine:junit-jupiter]"); var engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration); - return new JupiterEngineExtensionContext(null, engineDescriptor, configuration, extensionRegistry, + return new JupiterEngineExtensionContext(mock(), engineDescriptor, configuration, extensionRegistry, launcherStoreFacade); }), // named("class", (JupiterConfiguration configuration) -> { var classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); var classTestDescriptor = new ClassTestDescriptor(classUniqueId, testClass, configuration); - return new ClassExtensionContext(null, null, classTestDescriptor, PER_METHOD, configuration, - extensionRegistry, launcherStoreFacade, null); + return new ClassExtensionContext(mock(AbstractExtensionContext.class), mock(), classTestDescriptor, + PER_METHOD, configuration, extensionRegistry, launcherStoreFacade, mock()); }), // named("method", (JupiterConfiguration configuration) -> { var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow(); var methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, List::of, configuration); - return new MethodExtensionContext(null, null, methodTestDescriptor, configuration, extensionRegistry, - launcherStoreFacade, null); + return new MethodExtensionContext(mock(AbstractExtensionContext.class), mock(), methodTestDescriptor, + configuration, extensionRegistry, launcherStoreFacade, mock()); }) // ); } @@ -478,7 +486,7 @@ private NestedClassTestDescriptor doublyNestedClassDescriptor() { OuterClassTestCase.NestedClass.DoublyNestedClass.class, List::of, configuration); } - private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) { + private ClassTestDescriptor outerClassDescriptor(@Nullable TestDescriptor child) { var classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), OuterClassTestCase.class, configuration); if (child != null) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LauncherStoreFacadeTest.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LauncherStoreFacadeTest.java index e140cb7329d5..ae6d12135362 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LauncherStoreFacadeTest.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LauncherStoreFacadeTest.java @@ -79,6 +79,7 @@ void returnsNamespaceAwareStore() { assertNotNull(adapter); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void throwsExceptionWhenNamespaceIsNull() { LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java index d4f56b9b684f..44f67c95609f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -251,6 +252,7 @@ void eight() { } @SuppressWarnings("JUnitMalformedDeclaration") +@NullUnmarked class TestCaseWithInvalidLifecycleMethods { @BeforeEach diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java index ce217f3b838f..717c40b9e5f6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ResourceAutoClosingTests.java @@ -39,7 +39,7 @@ void shouldCloseAutoCloseableWhenIsClosingStoredAutoCloseablesEnabledIsTrue() th AutoCloseableResource resource = new AutoCloseableResource(); when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(true); - ExtensionContext extensionContext = new JupiterEngineExtensionContext(null, testDescriptor, configuration, + ExtensionContext extensionContext = new JupiterEngineExtensionContext(mock(), testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); store.put("resource", resource); @@ -54,7 +54,7 @@ void shouldNotCloseAutoCloseableWhenIsClosingStoredAutoCloseablesEnabledIsFalse( AutoCloseableResource resource = new AutoCloseableResource(); when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(false); - ExtensionContext extensionContext = new JupiterEngineExtensionContext(null, testDescriptor, configuration, + ExtensionContext extensionContext = new JupiterEngineExtensionContext(mock(), testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); store.put("resource", resource); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index d072738824d9..da14c6c3ae6c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -163,7 +163,7 @@ void before() throws Exception { extensionContext = mock(); isClosed = false; - context = new JupiterEngineExecutionContext(null, null, null) // + context = new JupiterEngineExecutionContext(mock(), mock(), mock()) // .extend() // .withThrowableCollector(new OpenTest4JAwareThrowableCollector()) // .withExtensionContext(extensionContext) // diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java index f16710ad35f7..2617a5153215 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java @@ -48,6 +48,7 @@ class TestInstanceLifecycleUtilsTests { private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index 9833fa18cb43..7f6053f76105 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -54,6 +54,7 @@ import java.util.function.Predicate; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.DisplayNameGenerator; @@ -98,6 +99,8 @@ class DiscoverySelectorResolverTests extends AbstractJupiterTestEngineTests { private final JupiterConfiguration configuration = mock(); private final LauncherDiscoveryListener discoveryListener = mock(); + + @Nullable private TestDescriptor engineDescriptor; @BeforeEach @@ -111,7 +114,7 @@ void setUp() { void nonTestClassResolution() { resolve(request().selectors(selectClass(NonTestClass.class))); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); } @Test @@ -119,7 +122,7 @@ void doesNotAttemptToResolveMethodsForNonTestClasses() { var methodSelector = selectMethod(NonTestClass.class, "doesNotExist"); resolve(request().selectors(methodSelector)); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(methodSelector); } @@ -127,7 +130,7 @@ void doesNotAttemptToResolveMethodsForNonTestClasses() { void abstractClassResolution() { resolve(request().selectors(selectClass(AbstractTestClass.class))); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(selectClass(AbstractTestClass.class)); } @@ -137,7 +140,7 @@ void singleClassResolution() { resolve(request().selectors(selector)); - assertEquals(4, engineDescriptor.getDescendants().size()); + assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); assertUniqueIdsForMyTestClass(uniqueIds()); } @@ -147,7 +150,7 @@ void classResolutionForNonexistentClass() { resolve(request().selectors(selector)); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow()).hasMessageContaining("Could not load class with name"); @@ -160,7 +163,7 @@ void duplicateClassSelectorOnlyResolvesOnce() { selectClass(MyTestClass.class) // )); - assertEquals(4, engineDescriptor.getDescendants().size()); + assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); assertUniqueIdsForMyTestClass(uniqueIds()); } @@ -171,7 +174,7 @@ void twoClassesResolution() { resolve(request().selectors(selector1, selector2)); - assertEquals(7, engineDescriptor.getDescendants().size()); + assertEquals(7, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertUniqueIdsForMyTestClass(uniqueIds); assertThat(uniqueIds).contains(uniqueIdForClass(YourTestClass.class)); @@ -192,7 +195,7 @@ void classResolutionOfStaticNestedClass() { resolve(request().selectors(selector)); - assertEquals(3, engineDescriptor.getDescendants().size()); + assertEquals(3, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); @@ -217,7 +220,7 @@ void classResolutionOfClassTemplate() { assertThat(verified.get()).describedAs("filter can see descendants").isTrue(); - TestDescriptor classTemplateDescriptor = getOnlyElement(engineDescriptor.getChildren()); + TestDescriptor classTemplateDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); assertThat(classTemplateDescriptor.mayRegisterTests()).isTrue(); assertThat(classTemplateDescriptor.getDescendants()).isEmpty(); @@ -233,17 +236,17 @@ void uniqueIdResolutionOfClassTemplateInvocation() { resolve(request().selectors(selector)); - assertThat(engineDescriptor.getChildren()).hasSize(1); + assertThat(requireNonNull(engineDescriptor).getChildren()).hasSize(1); - TestDescriptor classTemplateDescriptor = getOnlyElement(engineDescriptor.getChildren()); + TestDescriptor classTemplateDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); classTemplateDescriptor.prune(); - assertThat(engineDescriptor.getChildren()).hasSize(1); + assertThat(requireNonNull(engineDescriptor).getChildren()).hasSize(1); assertThat(classTemplateDescriptor.mayRegisterTests()).isTrue(); assertThat(classTemplateDescriptor.getDescendants()).isEmpty(); classTemplateDescriptor.prune(); - assertThat(engineDescriptor.getChildren()).hasSize(1); + assertThat(requireNonNull(engineDescriptor).getChildren()).hasSize(1); assertThat(classTemplateDescriptor.mayRegisterTests()).isTrue(); assertThat(classTemplateDescriptor.getDescendants()).isEmpty(); } @@ -255,7 +258,7 @@ void methodResolution() throws NoSuchMethodException { resolve(request().selectors(selector)); - assertEquals(2, engineDescriptor.getDescendants().size()); + assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); @@ -267,7 +270,7 @@ void methodResolutionFromInheritedMethod() throws NoSuchMethodException { resolve(request().selectors(selector)); - assertEquals(2, engineDescriptor.getDescendants().size()); + assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test1()")); @@ -280,7 +283,7 @@ void resolvingSelectorOfNonTestMethodResolvesNothing() throws NoSuchMethodExcept resolve(request().selectors(selector)); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); } @Test @@ -291,7 +294,7 @@ void methodResolutionForNonexistentClass() { resolve(request().selectors(selector)); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// @@ -305,7 +308,7 @@ void methodResolutionForNonexistentMethod() { resolve(request().selectors(selector)); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selector); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow()).hasMessageContaining("Could not find method"); @@ -317,7 +320,7 @@ void classResolutionByUniqueId() { resolve(request().selectors(selector)); - assertEquals(4, engineDescriptor.getDescendants().size()); + assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertUniqueIdsForMyTestClass(uniqueIds); } @@ -328,7 +331,7 @@ void staticNestedClassResolutionByUniqueId() { resolve(request().selectors(selector)); - assertEquals(3, engineDescriptor.getDescendants().size()); + assertEquals(3, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); @@ -342,7 +345,7 @@ void methodOfInnerClassByUniqueId() { resolve(request().selectors(selector)); - assertEquals(2, engineDescriptor.getDescendants().size()); + assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(OtherTestClass.NestedTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(OtherTestClass.NestedTestClass.class, "test5()")); @@ -355,7 +358,7 @@ void resolvingUniqueIdWithUnknownSegmentTypeResolvesNothing() { resolve(request().selectors(selector)); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(selector); } @@ -365,7 +368,7 @@ void resolvingUniqueIdOfNonTestMethodResolvesNothing() { resolve(request().selectors(selector)); - assertThat(engineDescriptor.getDescendants()).isEmpty(); + assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); assertUnresolved(selector); } @@ -375,7 +378,7 @@ void methodResolutionByUniqueIdWithMissingMethodName() { resolve(request().selectors(selectUniqueId(uniqueId))); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selectUniqueId(uniqueId)); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// @@ -389,7 +392,7 @@ void methodResolutionByUniqueIdWithMissingParameters() { resolve(request().selectors(selectUniqueId(uniqueId))); - assertThat(engineDescriptor.getDescendants()).isEmpty(); + assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); var result = verifySelectorProcessed(selectUniqueId(uniqueId)); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// @@ -403,7 +406,7 @@ void methodResolutionByUniqueIdWithBogusParameters() { resolve(request().selectors(selectUniqueId(uniqueId))); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); var result = verifySelectorProcessed(selectUniqueId(uniqueId)); assertThat(result.getStatus()).isEqualTo(FAILED); assertThat(result.getThrowable().orElseThrow())// @@ -418,7 +421,7 @@ void methodResolutionByUniqueId() { resolve(request().selectors(selector)); - assertEquals(2, engineDescriptor.getDescendants().size()); + assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); @@ -430,7 +433,7 @@ void methodResolutionByUniqueIdFromInheritedClass() { resolve(request().selectors(selector)); - assertEquals(2, engineDescriptor.getDescendants().size()); + assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); @@ -444,7 +447,7 @@ void methodResolutionByUniqueIdWithParams() { resolve(request().selectors(selector)); - assertEquals(2, engineDescriptor.getDescendants().size()); + assertEquals(2, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(HerTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(HerTestClass.class, "test7(java.lang.String)")); @@ -457,7 +460,7 @@ void resolvingUniqueIdWithWrongParamsResolvesNothing() { resolve(request().selectors(selectUniqueId(uniqueId))); - assertTrue(engineDescriptor.getDescendants().isEmpty()); + assertTrue(requireNonNull(engineDescriptor).getDescendants().isEmpty()); assertUnresolved(selectUniqueId(uniqueId)); } @@ -469,7 +472,7 @@ void twoMethodResolutionsByUniqueId() { // adding same selector twice should have no effect resolve(request().selectors(selector1, selector2, selector2)); - assertEquals(3, engineDescriptor.getDescendants().size()); + assertEquals(3, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(MyTestClass.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(MyTestClass.class, "test1()")); @@ -490,7 +493,7 @@ void packageResolutionUsingExplicitBasePackage() { resolve(request().selectors(selector)); - assertEquals(6, engineDescriptor.getDescendants().size()); + assertEquals(6, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); @@ -505,7 +508,7 @@ void packageResolutionUsingDefaultPackage() throws Exception { resolve(request().selectors(selectPackage(""))); // 150 is completely arbitrary. The actual number is likely much higher. - assertThat(engineDescriptor.getDescendants())// + assertThat(requireNonNull(engineDescriptor).getDescendants())// .describedAs("Too few test descriptors in classpath")// .hasSizeGreaterThan(150); @@ -529,14 +532,14 @@ void classpathResolution() throws Exception { resolve(request().selectors(selectors)); // 150 is completely arbitrary. The actual number is likely much higher. - assertThat(engineDescriptor.getDescendants())// + assertThat(requireNonNull(engineDescriptor).getDescendants())// .describedAs("Too few test descriptors in classpath")// .hasSizeGreaterThan(150); List uniqueIds = uniqueIds(); assertThat(uniqueIds)// .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// - .contains(uniqueIdForClass(ReflectionSupport.tryToLoadClass("DefaultPackageTestCase").get())); + .contains(uniqueIdForClass(ReflectionSupport.tryToLoadClass("DefaultPackageTestCase").getNonNull())); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); @@ -641,7 +644,7 @@ void methodResolutionInDoubleNestedTestClass() throws NoSuchMethodException { resolve(request().selectors(selector)); - assertEquals(4, engineDescriptor.getDescendants().size()); + assertEquals(4, requireNonNull(engineDescriptor).getDescendants().size()); List uniqueIds = uniqueIds(); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.class)); assertThat(uniqueIds).contains(uniqueIdForClass(TestCaseWithNesting.NestedTestCase.class)); @@ -671,7 +674,7 @@ void testFactoryMethodResolutionByUniqueId() { resolve(request().selectors(selectUniqueId(factoryUid))); - assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); } @@ -682,7 +685,7 @@ void testTemplateMethodResolutionByUniqueId() { resolve(request().selectors(selectUniqueId(templateUid))); - assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); } @@ -695,9 +698,9 @@ void resolvingDynamicTestByUniqueIdResolvesUpToParentTestFactory() { resolve(request().selectors(selectUniqueId(dynamicTestUid))); - assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + TestDescriptor testClassDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); @@ -718,9 +721,9 @@ void resolvingDynamicContainerByUniqueIdResolvesUpToParentTestFactory() { resolve(request().selectors(selectUniqueId(dynamicTestUid))); - assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + TestDescriptor testClassDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); @@ -739,9 +742,9 @@ void resolvingDynamicTestByUniqueIdAndTestFactoryByMethodSelectorResolvesTestFac resolve(request().selectors(selectUniqueId(dynamicTestUid), selectMethod(clazz, "dynamicTest"))); - assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), factoryUid); - TestDescriptor testClassDescriptor = getOnlyElement(engineDescriptor.getChildren()); + TestDescriptor testClassDescriptor = getOnlyElement(requireNonNull(engineDescriptor).getChildren()); TestDescriptor testFactoryDescriptor = getOnlyElement(testClassDescriptor.getChildren()); DynamicDescendantFilter dynamicDescendantFilter = getDynamicDescendantFilter(testFactoryDescriptor); assertThat(dynamicDescendantFilter.test(UniqueId.root("foo", "bar"), 42)).isTrue(); @@ -760,7 +763,7 @@ void resolvingTestTemplateInvocationByUniqueIdResolvesOnlyUpToParentTestTemplate resolve(request().selectors(selectUniqueId(invocationUid))); - assertThat(engineDescriptor.getDescendants()).hasSize(2); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(2); assertThat(uniqueIds()).containsSequence(uniqueIdForClass(clazz), templateUid); } @@ -769,7 +772,7 @@ void includingPackageNameFilterExcludesClassesInNonMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( includePackageNames("org.junit.jupiter.engine.unknown"))); - assertThat(engineDescriptor.getDescendants()).isEmpty(); + assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); } @Test @@ -777,7 +780,7 @@ void includingPackageNameFilterIncludesClassesInMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( includePackageNames("org.junit.jupiter.engine"))); - assertThat(engineDescriptor.getDescendants()).hasSize(3); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(3); } @Test @@ -785,7 +788,7 @@ void excludingPackageNameFilterExcludesClassesInMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( excludePackageNames("org.junit.jupiter.engine"))); - assertThat(engineDescriptor.getDescendants()).isEmpty(); + assertThat(requireNonNull(engineDescriptor).getDescendants()).isEmpty(); } @Test @@ -793,7 +796,7 @@ void excludingPackageNameFilterIncludesClassesInNonMatchingPackages() { resolve(request().selectors(selectClass(MatchingClass.class)).filters( excludePackageNames("org.junit.jupiter.engine.unknown"))); - assertThat(engineDescriptor.getDescendants()).hasSize(3); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(3); } @Test @@ -801,7 +804,7 @@ void classNamePatternFilterExcludesNonMatchingClasses() { resolve(request().selectors(selectClass(MatchingClass.class), selectClass(OtherClass.class)).filters( includeClassNamePatterns(".*MatchingClass"))); - assertThat(engineDescriptor.getDescendants()).hasSize(3); + assertThat(requireNonNull(engineDescriptor).getDescendants()).hasSize(3); } private void resolve(LauncherDiscoveryRequestBuilder builder) { @@ -809,12 +812,12 @@ private void resolve(LauncherDiscoveryRequestBuilder builder) { } private TestDescriptor descriptorByUniqueId(UniqueId uniqueId) { - return engineDescriptor.getDescendants().stream().filter( + return requireNonNull(engineDescriptor).getDescendants().stream().filter( d -> d.getUniqueId().equals(uniqueId)).findFirst().orElseThrow(); } private List uniqueIds() { - return engineDescriptor.getDescendants().stream().map(TestDescriptor::getUniqueId).toList(); + return requireNonNull(engineDescriptor).getDescendants().stream().map(TestDescriptor::getUniqueId).toList(); } private LauncherDiscoveryRequestBuilder request() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java index c054f3ec1220..18291aefb8d6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java @@ -23,6 +23,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterResolver; @@ -39,6 +40,8 @@ abstract class AbstractExecutableInvokerTests { private static final String ENIGMA = "enigma"; protected final MethodSource instance = mock(); + + @Nullable protected Method method; protected final ExtensionContext extensionContext = mock(); @@ -84,7 +87,7 @@ private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument private void testMethodWithASingleStringParameter() { this.method = ReflectionSupport.findMethod(this.instance.getClass(), "singleStringParameter", - String.class).get(); + String.class).orElseThrow(); } private void register(ParameterResolver... resolvers) { @@ -95,6 +98,6 @@ private void register(ParameterResolver... resolvers) { abstract void invokeMethod(); - abstract T invokeConstructor(Constructor constructor, Object outerInstance); + abstract T invokeConstructor(Constructor constructor, @Nullable Object outerInstance); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java index c9db82e0dd9c..27cc736eb43d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java @@ -10,8 +10,12 @@ package org.junit.jupiter.engine.execution; +import static java.util.Objects.requireNonNull; + import java.lang.reflect.Constructor; +import org.jspecify.annotations.Nullable; + /** * Unit tests for {@link DefaultExecutableInvoker}. * @@ -21,11 +25,11 @@ class DefaultExecutableInvokerTests extends AbstractExecutableInvokerTests { @Override void invokeMethod() { - newInvoker().invoke(this.method, this.instance); + newInvoker().invoke(requireNonNull(this.method), this.instance); } @Override - T invokeConstructor(Constructor constructor, Object outerInstance) { + T invokeConstructor(Constructor constructor, @Nullable Object outerInstance) { return newInvoker().invoke(constructor, outerInstance); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java index 58d96e0ecce1..a3abef9708e2 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java @@ -10,10 +10,13 @@ package org.junit.jupiter.engine.execution; +import static java.util.Objects.requireNonNull; + import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; /** @@ -25,12 +28,12 @@ class InterceptingExecutableInvokerTests extends AbstractExecutableInvokerTests @Override void invokeMethod() { - newInvoker().invoke(this.method, this.instance, this.extensionContext, this.extensionRegistry, + newInvoker().invoke(requireNonNull(this.method), this.instance, this.extensionContext, this.extensionRegistry, passthroughInterceptor()); } @Override - T invokeConstructor(Constructor constructor, Object outerInstance) { + T invokeConstructor(Constructor constructor, @Nullable Object outerInstance) { return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), __ -> extensionContext, extensionRegistry, passthroughInterceptor()); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java index 54e175b8bc6f..cd68a46daf63 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.engine.execution; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -25,6 +26,7 @@ import java.util.function.Predicate; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; @@ -45,6 +47,8 @@ class ParameterResolutionUtilsTests { private static final String ENIGMA = "enigma"; private final MethodSource instance = mock(); + + @Nullable private Method method; private final ExtensionContext extensionContext = mock(); @@ -142,6 +146,7 @@ void onlyConsiderParameterResolversThatSupportAParticularParameter() { assertThat(arguments).containsExactly("something"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void passContextInformationToParameterResolverMethods() { anyTestMethodWithAtLeastOneParameter(); @@ -152,10 +157,10 @@ void passContextInformationToParameterResolverMethods() { assertSame(extensionContext, extension.supportsArguments.extensionContext); assertEquals(0, extension.supportsArguments.parameterContext.getIndex()); - assertSame(instance, extension.supportsArguments.parameterContext.getTarget().get()); + assertSame(instance, extension.supportsArguments.parameterContext.getTarget().orElseThrow()); assertSame(extensionContext, extension.resolveArguments.extensionContext); assertEquals(0, extension.resolveArguments.parameterContext.getIndex()); - assertSame(instance, extension.resolveArguments.parameterContext.getTarget().get()); + assertSame(instance, extension.resolveArguments.parameterContext.getTarget().orElseThrow()); assertThat(extension.resolveArguments.parameterContext.toString())// .contains("parameter", String.class.getTypeName(), "index", "0", "target", "Mock"); } @@ -289,7 +294,7 @@ private void throwDuringParameterResolution(RuntimeException parameterResolution register(ConfigurableParameterResolver.onAnyCallThrow(parameterResolutionException)); } - private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument) { + private void thereIsAParameterResolverThatResolvesTheParameterTo(@Nullable Object argument) { register(ConfigurableParameterResolver.supportsAndResolvesTo(parameterContext -> argument)); } @@ -323,14 +328,14 @@ private void register(ParameterResolver... resolvers) { } } - private Object[] resolveConstructorParameters(Class clazz, Object outerInstance) { + private @Nullable Object[] resolveConstructorParameters(Class clazz, @Nullable Object outerInstance) { Constructor constructor = ReflectionUtils.getDeclaredConstructor(clazz); return ParameterResolutionUtils.resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), extensionContext, extensionRegistry); } - private Object[] resolveMethodParameters() { - return ParameterResolutionUtils.resolveParameters(this.method, Optional.of(this.instance), + private @Nullable Object[] resolveMethodParameters() { + return ParameterResolutionUtils.resolveParameters(requireNonNull(this.method), Optional.of(this.instance), this.extensionContext, this.extensionRegistry); } @@ -338,8 +343,8 @@ private Object[] resolveMethodParameters() { static class ArgumentRecordingParameterResolver implements ParameterResolver { - ArgumentRecordingParameterResolver.Arguments supportsArguments; - ArgumentRecordingParameterResolver.Arguments resolveArguments; + ArgumentRecordingParameterResolver.@Nullable Arguments supportsArguments; + ArgumentRecordingParameterResolver.@Nullable Arguments resolveArguments; static class Arguments { @@ -359,7 +364,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { resolveArguments = new ArgumentRecordingParameterResolver.Arguments(parameterContext, extensionContext); return null; } @@ -375,7 +380,7 @@ static ParameterResolver onAnyCallThrow(RuntimeException runtimeException) { }); } - static ParameterResolver supportsAndResolvesTo(Function resolve) { + static ParameterResolver supportsAndResolvesTo(Function resolve) { return new ConfigurableParameterResolver(parameterContext -> true, resolve); } @@ -386,10 +391,10 @@ static ParameterResolver withoutSupport() { } private final Predicate supports; - private final Function resolve; + private final Function resolve; private ConfigurableParameterResolver(Predicate supports, - Function resolve) { + Function resolve) { this.supports = supports; this.resolve = resolve; } @@ -400,7 +405,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon } @Override - public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + public @Nullable Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return resolve.apply(parameterContext); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java index 53b754d8b99b..2c2fc7476e13 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java @@ -28,6 +28,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.BeforeAll; @@ -422,6 +423,7 @@ static class ArrayFieldTestCase implements TestInterface { final int[] x = {}; } + @NullUnmarked static class NullCloseableFieldTestCase implements TestInterface { @AutoClose @@ -456,6 +458,7 @@ static class CloseMethodMustBeInvokedViaInterfaceTestCase implements TestInterfa @TestInstance(PER_METHOD) @SuppressWarnings("JUnitMalformedDeclaration") + @NullUnmarked static class InstancePerMethodTestCase { @AutoClose @@ -694,6 +697,7 @@ void nestedTest() { } } + @NullUnmarked static class AutoCloseSpy implements AutoCloseable, Runnable { private final String prefix; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java index 34ebef190656..0075ace93ab2 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -38,6 +39,7 @@ class BeforeAndAfterAllTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); + @Nullable private static Optional actualExceptionInAfterAllCallback; @Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java index 0b320af3dbb4..372f8199ff8b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -42,8 +43,10 @@ class BeforeAndAfterEachTests extends AbstractJupiterTestEngineTests { private static final List callSequence = new ArrayList<>(); private static final List beforeEachMethodCallSequence = new ArrayList<>(); + @Nullable private static Optional actualExceptionInAfterEachCallback; + @SuppressWarnings("OptionalAssignedToNull") @BeforeEach void resetCallSequence() { callSequence.clear(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java index 119b2ecb6909..dd13d741e1ea 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -42,10 +43,12 @@ */ class BeforeAndAfterTestExecutionCallbackTests extends AbstractJupiterTestEngineTests { - private static List callSequence = new ArrayList<>(); + private static final List callSequence = new ArrayList<>(); + @Nullable private static Optional actualExceptionInAfterTestExecution; + @SuppressWarnings("OptionalAssignedToNull") @BeforeEach void resetCallSequence() { callSequence.clear(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java index 5c66048d03f6..ad86b2cf460a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java @@ -52,6 +52,8 @@ import com.google.common.jimfs.Jimfs; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -68,6 +70,7 @@ import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.engine.extension.TempDirectory.CloseablePath; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; @@ -86,7 +89,8 @@ class CloseablePathTests extends AbstractJupiterTestEngineTests { private final AnnotatedElementContext elementContext = mock(); private final ExtensionContext extensionContext = mock(); - private TempDirectory.CloseablePath closeablePath; + @Nullable + private CloseablePath closeablePath; @Target(METHOD) @Retention(RUNTIME) @@ -164,6 +168,7 @@ void factoryReturnsDirectoryOnNonDefaultFileSystemWithPath() throws IOException delete(closeablePath.get()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @DisplayName("fails if the factory returns null") @ParameterizedTest @ElementTypeSource @@ -276,7 +281,9 @@ class Cleanup { @AfterEach void cleanupTempDirectory() throws IOException { - deleteIfExists(closeablePath.get()); + if (closeablePath != null) { + deleteIfExists(closeablePath.get()); + } } @DisplayName("is done for a cleanup mode of ALWAYS") @@ -457,6 +464,7 @@ void deletesSymbolicLinksTargetingDirOutsideTempDir(Class elementType, } } + @NullUnmarked static class TestCase { Path tempDir; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java index c3214cf18aad..5f3281917664 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java @@ -34,13 +34,13 @@ void extensionContextHierarchy(ExtensionContext methodExtensionContext) { Optional classExtensionContext = methodExtensionContext.getParent(); assertThat(classExtensionContext).isNotEmpty(); - assertThat(classExtensionContext.orElse(null).getElement()).contains(ExtensionContextExecutionTests.class); + assertThat(classExtensionContext.orElseThrow().getElement()).contains(ExtensionContextExecutionTests.class); - Optional engineExtensionContext = classExtensionContext.orElse(null).getParent(); + Optional engineExtensionContext = classExtensionContext.orElseThrow().getParent(); assertThat(engineExtensionContext).isNotEmpty(); - assertThat(engineExtensionContext.orElse(null).getElement()).isEmpty(); + assertThat(engineExtensionContext.orElseThrow().getElement()).isEmpty(); - assertThat(engineExtensionContext.orElse(null).getParent()).isEmpty(); + assertThat(engineExtensionContext.orElseThrow().getParent()).isEmpty(); } @Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java index 0a72e2dd4212..7be354083dfc 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java @@ -40,6 +40,8 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -596,6 +598,7 @@ void test(Long number) { } } + @NullUnmarked static class MultipleExtendWithRegistrationsViaFieldTestCase { @SuppressWarnings("unused") @@ -623,6 +626,7 @@ void test() { /** * The {@link MagicField.Extension} is registered via a static field. */ + @NullUnmarked static class StaticFieldTestCase { @SuppressWarnings("unused") @@ -648,6 +652,7 @@ void test() { /** * The {@link MagicField.Extension} is registered via an instance field. */ + @NullUnmarked static class InstanceFieldTestCase { @MagicField @@ -667,6 +672,7 @@ void test() { * The {@link MagicField.Extension} is registered via a static field and * an instance field. */ + @NullUnmarked @TestInstance(Lifecycle.PER_CLASS) static class TestInstancePerClassFieldTestCase { @@ -689,6 +695,7 @@ void test() { } } + @NullUnmarked @TestInstance(Lifecycle.PER_METHOD) static class AllInOneWithTestInstancePerMethodTestCase { @@ -779,6 +786,7 @@ static class AllInOneWithTestInstancePerClassTestCase extends AllInOneWithTestIn } } + @NullUnmarked static class ProgrammaticTestInstancePostProcessorTestCase { @RegisterExtension @@ -954,7 +962,8 @@ public final void postProcessTestInstance(Object testInstance, ExtensionContext ModifierSupport::isNotStatic); } - private void injectFields(String trigger, Class testClass, Object instance, Predicate predicate) { + private void injectFields(String trigger, Class testClass, @Nullable Object instance, + Predicate predicate) { findAnnotatedFields(testClass, this.annotationType, predicate).forEach(field -> { try { makeAccessible(field).set(instance, trigger + " - " + field.getName()); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java index b1e163d7cfa9..1954b93d35e0 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java @@ -23,6 +23,7 @@ import java.util.logging.LogRecord; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.ClassOrderer; @@ -209,12 +210,12 @@ private static void assertIneffectiveOrderAnnotationIssues(List .containsExactlyInAnyOrder(ClassSource.from(A_TestCase.class), ClassSource.from(C_TestCase.class)); } - private Events executeTests(Class classOrderer) { + private Events executeTests(@Nullable Class classOrderer) { return executeTests(classOrderer, selectClass(A_TestCase.class), selectClass(B_TestCase.class), selectClass(C_TestCase.class)); } - private Events executeTests(Class classOrderer, DiscoverySelector... selectors) { + private Events executeTests(@Nullable Class classOrderer, DiscoverySelector... selectors) { // @formatter:off return testKit(classOrderer, selectors) .execute() @@ -222,17 +223,17 @@ private Events executeTests(Class classOrderer, Discover // @formatter:on } - private EngineDiscoveryResults discoverTests(Class classOrderer) { + private EngineDiscoveryResults discoverTests(@Nullable Class classOrderer) { return discoverTests(classOrderer, selectClass(A_TestCase.class), selectClass(B_TestCase.class), selectClass(C_TestCase.class)); } - private EngineDiscoveryResults discoverTests(Class classOrderer, + private EngineDiscoveryResults discoverTests(@Nullable Class classOrderer, DiscoverySelector... selectors) { return testKit(classOrderer, selectors).discover(); } - private static EngineTestKit.Builder testKit(Class classOrderer, + private static EngineTestKit.Builder testKit(@Nullable Class classOrderer, DiscoverySelector[] selectors) { var testKit = EngineTestKit.engine("junit-jupiter"); @@ -246,7 +247,7 @@ static abstract class BaseTestCase { @BeforeEach void trackInvocations(TestInfo testInfo) { - var testClass = testInfo.getTestClass().get(); + var testClass = testInfo.getTestClass().orElseThrow(); callSequence.add(testClass.getSimpleName()); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java index 3085d59cb66c..bd1b61c9446b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java @@ -34,6 +34,7 @@ import java.util.logging.LogRecord; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; @@ -323,23 +324,24 @@ void misbehavingMethodOrdererThatRemovesElements() { .containsSubsequence("test2()", "test4()");// removed item is re-added before ordered item } - private EngineDiscoveryResults discoverTests(Class testClass, Class defaultOrderer) { + private EngineDiscoveryResults discoverTests(Class testClass, + @Nullable Class defaultOrderer) { return testKit(testClass, defaultOrderer, Severity.INFO).discover(); } - private Events executeTestsInParallel(Class testClass, Class defaultOrderer) { + private Events executeTestsInParallel(Class testClass, @Nullable Class defaultOrderer) { return executeTestsInParallel(testClass, defaultOrderer, Severity.INFO); } - private Events executeTestsInParallel(Class testClass, Class defaultOrderer, + private Events executeTestsInParallel(Class testClass, @Nullable Class defaultOrderer, Severity criticalSeverity) { return testKit(testClass, defaultOrderer, criticalSeverity) // .execute() // .testEvents(); } - private static EngineTestKit.Builder testKit(Class testClass, Class defaultOrderer, - Severity criticalSeverity) { + private static EngineTestKit.Builder testKit(Class testClass, + @Nullable Class defaultOrderer, Severity criticalSeverity) { var testKit = EngineTestKit.engine("junit-jupiter") // .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") // .configurationParameter(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent") // @@ -740,7 +742,7 @@ public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().set(1, createMethodDescriptorImpersonator(method2)); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "DataFlowIssue", "NullAway" }) static T createMethodDescriptorImpersonator(MethodDescriptor method) { MethodDescriptor stub = new MethodDescriptor() { @Override @@ -760,12 +762,12 @@ public boolean isAnnotated(Class annotationType) { @Override public Optional findAnnotation(Class annotationType) { - return null; + return Optional.empty(); } @Override public List findRepeatableAnnotations(Class annotationType) { - return null; + return List.of(); } @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java index e168d58cc974..49519b77b131 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java @@ -29,6 +29,8 @@ import java.util.function.Predicate; import org.assertj.core.api.Condition; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -221,7 +223,7 @@ void classLevelWithNonExtensionFieldValue() { instanceOf(PreconditionViolationException.class), message(expectedMessage(testClass, String.class)))); } - private String expectedMessage(Class testClass, Class valueType) { + private String expectedMessage(Class testClass, @Nullable Class valueType) { return "Failed to register extension via @RegisterExtension field [" + field(testClass) + "]: field value's type [" + (valueType != null ? valueType.getName() : null) + "] must implement an [" + Extension.class.getName() + "] API."; @@ -324,6 +326,7 @@ void afterEach(String wisdom) { @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExtensionInjector.class) + @NullUnmarked static class InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase { @RegisterExtension @@ -629,6 +632,7 @@ static class ClassLevelExtensionRegistrationWithPrivateFieldTestCase extends Abs } + @NullUnmarked static class InstanceLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { @RegisterExtension @@ -636,6 +640,7 @@ static class InstanceLevelExtensionRegistrationWithNullFieldTestCase extends Abs } + @NullUnmarked static class ClassLevelExtensionRegistrationWithNullFieldTestCase extends AbstractTestCase { @RegisterExtension diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java index 04e2735f4805..b74afe95b6ec 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java @@ -36,6 +36,7 @@ class SeparateThreadTimeoutInvocationTests { private static final long PREEMPTIVE_TIMEOUT_MILLIS = WINDOWS.isCurrentOs() ? 1000 : 100; + @SuppressWarnings("NullAway") @Test @DisplayName("throws timeout exception when timeout duration is exceeded") void throwsTimeoutException() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java index b8d0b90468aa..5619832335ba 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -27,6 +27,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; @@ -53,6 +54,7 @@ class TempDirectoryCleanupTests extends AbstractJupiterTestEngineTests { @Nested + @NullUnmarked class TempDirFieldTests { private static Path defaultFieldDir; @@ -315,6 +317,7 @@ void passingTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { } @Nested + @NullUnmarked class TempDirParameterTests { private static Path defaultParameterDir; @@ -539,6 +542,7 @@ void doesNotFollowJunctions(@TempDir Path tempDir, @TrackLogRecords LogRecordLis } @SuppressWarnings("JUnitMalformedDeclaration") + @NullUnmarked static class JunctionTestCase { public static Path target; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java index 338ba7dd286a..7e93ae45728f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -45,6 +46,7 @@ void annotationOnParameter() { } @SuppressWarnings("JUnitMalformedDeclaration") + @NullUnmarked static class AnnotationOnFieldTestCase { @CustomTempDir diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryTests.java index 394f7005e298..ea21ce0abe14 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryTests.java @@ -14,6 +14,7 @@ import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -51,6 +52,8 @@ import com.google.common.jimfs.Jimfs; import org.assertj.core.api.Condition; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -110,6 +113,7 @@ void resetStaticVariables() { AllPossibleDeclarationLocationsTestCase.tempDirs.clear(); } + @SuppressWarnings("NullAway") @ParameterizedTest(name = "{0}") @EnumSource(TestInstance.Lifecycle.class) @DisplayName("resolves separate temp dirs for each annotation declaration") @@ -352,6 +356,7 @@ void doesNotSupportCustomDefaultTempDirFactoryNotReturningDirectory() { private static class FactoryNotReturningDirectory implements TempDirFactory { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { return null; @@ -1112,6 +1117,7 @@ void deeplyNested() { } @SuppressWarnings("JUnitMalformedDeclaration") + @NullUnmarked static class StaticTempDirUsageInsideNestedClassTestCase { @TempDir @@ -1308,6 +1314,7 @@ void test(@TempDir(factory = Factory.class) Path tempDir) { private static class Factory implements TempDirFactory { + @Nullable private static Path parent; private Factory() throws IOException { @@ -1317,7 +1324,7 @@ private Factory() throws IOException { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { - return Files.createTempDirectory(parent, "prefix"); + return Files.createTempDirectory(requireNonNull(parent), "prefix"); } } @@ -1334,6 +1341,7 @@ void test(@TempDir(factory = Factory.class) Path tempDir) { private static class Factory implements TempDirFactory { + @Nullable private static FileSystem fileSystem; @Override @@ -1345,7 +1353,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio @Override public void close() throws IOException { - fileSystem.close(); + requireNonNull(fileSystem).close(); fileSystem = null; } } @@ -1363,6 +1371,7 @@ void test(@TempDir(factory = Factory.class) Path tempDir) { private static class Factory implements TempDirFactory { + @Nullable private static FileSystem fileSystem; @Override @@ -1374,7 +1383,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio @Override public void close() throws IOException { - fileSystem.close(); + requireNonNull(fileSystem).close(); fileSystem = null; } } @@ -1410,6 +1419,7 @@ private static String getName(AnnotatedElement element) { } @SuppressWarnings("JUnitMalformedDeclaration") + @NullUnmarked static class FactoryWithCustomMetaAnnotationTestCase { @TempDirForField @@ -1464,6 +1474,7 @@ void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tem // never called } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) private static class Factory implements TempDirFactory { @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java index 8433ad7f5824..ac92484ef037 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java @@ -775,6 +775,7 @@ private static class LegacyInstanceFactory extends AbstractTestInstanceFactory { */ private static class NullTestInstanceFactory implements TestInstanceFactory { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) { return null; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java index f785fb791744..c6b811a1d684 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java @@ -31,6 +31,7 @@ class TestReporterParameterResolverTests { TestReporterParameterResolver resolver = new TestReporterParameterResolver(); + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void supports() { Parameter parameter1 = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); @@ -49,7 +50,7 @@ void resolve() { } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).get(); + Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).orElseThrow(); return method.getParameters()[0]; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java index 05d318c12418..49a3bb073554 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java @@ -30,6 +30,7 @@ import java.util.logging.LogRecord; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; @@ -385,12 +386,12 @@ public void testSuccessful(ExtensionContext context) { } @Override - public void testAborted(ExtensionContext context, Throwable cause) { + public void testAborted(ExtensionContext context, @Nullable Throwable cause) { trackResult("testAborted", context); } @Override - public void testFailed(ExtensionContext context, Throwable cause) { + public void testFailed(ExtensionContext context, @Nullable Throwable cause) { trackResult("testFailed", context); } @@ -419,12 +420,12 @@ public void testDisabled(ExtensionContext context, Optional reason) { } @Override - public void testAborted(ExtensionContext context, Throwable cause) { + public void testAborted(ExtensionContext context, @Nullable Throwable cause) { throw new JUnitException("Exception in testAborted()"); } @Override - public void testFailed(ExtensionContext context, Throwable cause) { + public void testFailed(ExtensionContext context, @Nullable Throwable cause) { throw new JUnitException("Exception in testFailed()"); } @@ -440,7 +441,7 @@ static class DataRetrievingTestWatcher implements BeforeTestExecutionCallback, T private static final String KEY = "key"; - private static final Map results = new HashMap<>(); + private static final Map results = new HashMap<>(); @Override public void beforeTestExecution(ExtensionContext context) throws Exception { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java index 3c5c5a29602e..5e0ef939dd0a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java @@ -51,6 +51,7 @@ void createExceptionWithMethodSignatureTimeoutAndThrowable() { .hasSuppressedException(suppressedException); } + @SuppressWarnings({ "DataFlowIssue", "NullAway", "ThrowableNotThrown" }) @Nested @DisplayName("throws exception when") class ThrowException { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java index 8cfd1f1c24d3..b88fcfac4343 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -37,6 +37,7 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -340,7 +341,8 @@ void reportsIllegalTimeoutDurations() { private static Execution findExecution(Events events, String displayName) { return events.executions()// .filter(execution -> execution.getTestDescriptor().getDisplayName().contains(displayName))// - .findFirst().get(); + .findFirst() // + .orElseThrow(); } @Nested @@ -618,6 +620,7 @@ void methodThatDoesNotThrowInterruptedException() { @SuppressWarnings("JUnitMalformedDeclaration") static class PlainTestCase { + @Nullable public static String slowMethod; @BeforeAll diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java index b630fd112054..87a1b0358298 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java @@ -61,6 +61,7 @@ void setUp() { timeoutInvocationFactory = new TimeoutInvocationFactory(store); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("throws exception when null store is provided on create") void shouldThrowExceptionWhenInstantiatingWithNullStore() { @@ -68,6 +69,7 @@ void shouldThrowExceptionWhenInstantiatingWithNullStore() { .hasMessage("store must not be null"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("throws exception when null timeout thread mode is provided on create") void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { @@ -75,6 +77,7 @@ void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { .hasMessage("thread mode must not be null"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("throws exception when null timeout invocation parameters is provided on create") void shouldThrowExceptionWhenNullTimeoutInvocationParametersIsProvidedWhenCreate() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java index ba149cdf0e1b..1ad5ee93778d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.migrationsupport.rules.FailAfterAllHelper.fail; +import org.jspecify.annotations.Nullable; import org.junit.Rule; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -52,6 +53,7 @@ public ExternalResource getResource2() { numberOfRule2InstancesCreated++; } + @Nullable private Object instance; @Override @@ -63,7 +65,7 @@ protected void before() { @Override protected void after() { assertNotNull(instance); - assertSame(instance, this); + assertSame(this, instance); afterOfRule2WasExecuted = true; } }; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java index 7d7946553b5e..f8dc13677f1f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java @@ -11,6 +11,7 @@ package org.junit.jupiter.params; import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -56,6 +57,7 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -944,11 +946,13 @@ static class FieldInjectionWithRegisteredConversionTestCase { @Test void test1() { + assertNotNull(value); assertTrue(value.startsWith("minus"), "negative"); } @Test void test2() { + assertNotNull(value); assertTrue(value.startsWith("minus"), "negative"); } } @@ -960,8 +964,8 @@ private static class CustomIntegerToStringConverter extends TypedArgumentConvert } @Override - protected String convert(Integer source) throws ArgumentConversionException { - return switch (source) { + protected String convert(@Nullable Integer source) throws ArgumentConversionException { + return switch (requireNonNull(source)) { case -1 -> "minus one"; case +1 -> "plus one"; default -> throw new IllegalArgumentException("Unsupported value: " + source); @@ -976,12 +980,12 @@ record RecordWithBuiltInAggregatorTestCase(ArgumentsAccessor accessor) { @Test void test1() { - assertTrue(accessor.getInteger(0) < 0, "negative"); + assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } @Test void test2() { - assertTrue(accessor.getInteger(0) < 0, "negative"); + assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } } @@ -996,12 +1000,12 @@ static class FieldInjectionWithBuiltInAggregatorTestCase { @Test void test1() { - assertTrue(accessor.getInteger(0) < 0, "negative"); + assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } @Test void test2() { - assertTrue(accessor.getInteger(0) < 0, "negative"); + assertTrue(requireNonNull(accessor.getInteger(0)) < 0, "negative"); } } @@ -1056,7 +1060,7 @@ protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetT AnnotatedElementContext context, int parameterIndex) throws ArgumentsAggregationException { assertThat(targetType).isEqualTo(int.class); - return accessor.getInteger(0) * 2; + return requireNonNull(accessor.getInteger(0)) * 2; } } @@ -1713,6 +1717,7 @@ void test(TestReporter reporter) { record ArgumentConversionPerInvocationConstructorInjectionTestCase( @ConvertWith(Wrapper.Converter.class) Wrapper wrapper) { + @Nullable static Wrapper instance; @BeforeAll @@ -1745,6 +1750,7 @@ private void setOrCheckWrapper() { @ValueSource(ints = 1) static class ArgumentConversionPerInvocationFieldInjectionTestCase { + @Nullable static Wrapper instance; @BeforeAll @@ -1780,8 +1786,8 @@ private void setOrCheckWrapper() { record Wrapper(int value) { static class Converter extends SimpleArgumentConverter { @Override - protected Object convert(Object source, Class targetType) { - return new Wrapper((Integer) source); + protected Object convert(@Nullable Object source, Class targetType) { + return new Wrapper((Integer) requireNonNull(source)); } } } @@ -1972,8 +1978,8 @@ void test2() { static class AtomicIntegerConverter extends SimpleArgumentConverter { @Override - protected Object convert(Object source, Class targetType) { - return new AtomicInteger((Integer) source); + protected Object convert(@Nullable Object source, Class targetType) { + return new AtomicInteger((Integer) requireNonNull(source)); } } @@ -2019,13 +2025,15 @@ void test() { class Converter implements ArgumentConverter { @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public @Nullable Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { assertNotNull(context.getParameter().getAnnotation(CustomConversion.class)); return source; } @Override - public Object convert(Object source, FieldContext context) throws ArgumentConversionException { + public @Nullable Object convert(@Nullable Object source, FieldContext context) + throws ArgumentConversionException { assertNotNull(context.getField().getAnnotation(CustomConversion.class)); return source; } @@ -2201,7 +2209,8 @@ void test() { static class ToStringConverter extends SimpleArgumentConverter { @Override - protected Object convert(Object source, Class targetType) throws ArgumentConversionException { + protected @Nullable Object convert(@Nullable Object source, Class targetType) + throws ArgumentConversionException { return source == null ? null : String.valueOf(source); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterTests.java index 7d48e198dfb2..82865ea6dfc4 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatterTests.java @@ -37,6 +37,8 @@ import java.util.Arrays; import java.util.Locale; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -346,6 +348,7 @@ private static String format(ParameterizedInvocationNameFormatter formatter, int return formatter.format(invocationIndex, EvaluatedArgumentSet.allOf(arguments)); } + @NullUnmarked private static class ToStringReturnsNull { @Override @@ -386,7 +389,7 @@ void processFruits(String fruit1, String fruit2) { private static class CustomAggregator extends SimpleArgumentsAggregator { @Override - protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, + protected @Nullable Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) { return accessor.get(0); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index 741a392c4caf..e6e49f6488ae 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.params.ParameterizedInvocationContextProvider.arguments; import static org.junit.jupiter.params.ParameterizedTestExtension.DECLARATION_CONTEXT_KEY; +import static org.mockito.Mockito.mock; import java.io.FileNotFoundException; import java.lang.reflect.AnnotatedElement; @@ -31,6 +32,7 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutableInvoker; @@ -47,6 +49,7 @@ import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @@ -133,7 +136,7 @@ public Stream provideArguments(ParameterDeclarations parame } }; - var exception = assertThrows(FileNotFoundException.class, () -> arguments(failingProvider, null, null)); + var exception = assertThrows(FileNotFoundException.class, () -> arguments(failingProvider, mock(), mock())); assertEquals("a message", exception.getMessage()); } @@ -236,17 +239,17 @@ public ExtensionContext getRoot() { @Override public String getUniqueId() { - return null; + throw new UnsupportedOperationException(); } @Override public String getDisplayName() { - return null; + return "display-name"; } @Override public Set getTags() { - return null; + throw new UnsupportedOperationException(); } @Override @@ -312,7 +315,7 @@ public Store getStore(Namespace namespace) { org.junit.platform.engine.support.store.Namespace.create(namespace.getParts())); method // .map(it -> new ParameterizedTestContext(testClass, it, - it.getAnnotation(ParameterizedTest.class))) // + AnnotationSupport.findAnnotation(it, ParameterizedTest.class).orElseThrow())) // .ifPresent(ctx -> store.put(DECLARATION_CONTEXT_KEY, ctx)); return store; } @@ -331,12 +334,12 @@ public ExecutionMode getExecutionMode() { public ExecutableInvoker getExecutableInvoker() { return new ExecutableInvoker() { @Override - public Object invoke(Method method, Object target) { - return null; + public Object invoke(Method method, @Nullable Object target) { + throw new UnsupportedOperationException(); } @Override - public T invoke(Constructor constructor, Object outerInstance) { + public T invoke(Constructor constructor, @Nullable Object outerInstance) { return ReflectionUtils.newInstance(constructor); } }; @@ -414,6 +417,7 @@ void method() { class NonStaticArgumentsProvider implements ArgumentsProvider { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { @@ -456,7 +460,7 @@ static class AmbiguousConstructorArgumentsProvider implements ArgumentsProvider @Override public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) { - return null; + throw new UnsupportedOperationException(); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 515f7eb87d6f..bf6653dce499 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -63,6 +63,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assumptions; @@ -928,8 +929,7 @@ void streamOfTwoDimensionalObjectArrays() { void reportsContainerWithAssumptionFailureInMethodSourceAsAborted() { execute("assumptionFailureInMethodSourceFactoryMethod", String.class).allEvents().assertThatEvents() // .haveExactly(1, event(container("test-template:assumptionFailureInMethodSourceFactoryMethod"), // - abortedWithReason(instanceOf(TestAbortedException.class), - message("Assumption failed: nothing to test")))); + abortedWithReason(instanceOf(TestAbortedException.class), message("nothing to test")))); } @Test @@ -1928,8 +1928,8 @@ void assumptionFailureInMethodSourceFactoryMethod(String test) { } static List assumptionFailureInMethodSourceFactoryMethod() { - Assumptions.assumeFalse(true, "nothing to test"); - return null; + Assumptions.abort("nothing to test"); + return List.of(); } } @@ -2508,7 +2508,8 @@ public Stream provideArguments(ParameterDeclarations parame record ArgumentConverterWithConstructorParameter(String value) implements ArgumentConverter { @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { return value; } } @@ -2593,7 +2594,7 @@ public Stream provideArguments(ParameterDeclarations parame private static class StringLengthConverter implements ArgumentConverter { @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { return String.valueOf(source).length(); } } @@ -2610,7 +2611,7 @@ protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetT private static class ErroneousConverter implements ArgumentConverter { @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public Object convert(@Nullable Object source, ParameterContext context) throws ArgumentConversionException { throw new ArgumentConversionException("something went horribly wrong"); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java index 999a101abd21..b90fe9521334 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params.aggregator; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; @@ -35,6 +36,8 @@ import java.util.Map; import java.util.stream.IntStream; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.extension.AnnotatedElementContext; @@ -225,6 +228,7 @@ private EngineExecutionResults execute(DiscoverySelector... selectors) { // ------------------------------------------------------------------------- + @NullUnmarked public static class Person { final String firstName; @@ -248,6 +252,7 @@ enum Gender { F, M } + @NullUnmarked static class Address { final String street; @@ -308,7 +313,7 @@ public Address aggregateArguments(ArgumentsAccessor arguments, Class targetTy return new Address( arguments.getString(startIndex + 0), arguments.getString(startIndex + 1), - arguments.getInteger(startIndex + 2) + requireNonNull(arguments.getInteger(startIndex + 2)) ); // @formatter:on } @@ -332,7 +337,7 @@ protected Map aggregateArguments(ArgumentsAccessor arguments, C static class NullAggregator extends SimpleArgumentsAggregator { @Override - protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, + protected @Nullable Object aggregateArguments(ArgumentsAccessor accessor, Class targetType, AnnotatedElementContext context, int parameterIndex) { Preconditions.condition(!targetType.isPrimitive(), () -> "only supports reference types"); return null; @@ -391,7 +396,8 @@ static class InstanceCountingConverter implements ArgumentConverter { } @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public @Nullable Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { return source; } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java index b5f6e941bc6e..8bad1f29d436 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java @@ -20,6 +20,7 @@ import java.util.Arrays; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.PreconditionViolationException; @@ -31,6 +32,7 @@ */ class DefaultArgumentsAccessorTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void argumentsMustNotBeNull() { assertThrows(PreconditionViolationException.class, () -> defaultArgumentsAccessor(1, (Object[]) null)); @@ -52,12 +54,12 @@ void indexMustBeSmallerThanLength() { @Test void getNull() { - assertNull(defaultArgumentsAccessor(1, new Object[] { null }).get(0)); + assertNull(defaultArgumentsAccessor(1, new @Nullable Object[] { null }).get(0)); } @Test void getWithNullCastToWrapperType() { - assertNull(defaultArgumentsAccessor(1, (Object[]) new Integer[] { null }).get(0, Integer.class)); + assertNull(defaultArgumentsAccessor(1, (Object[]) new @Nullable Integer[] { null }).get(0, Integer.class)); } @Test @@ -79,7 +81,7 @@ void getWithCastToPrimitiveType() { "Argument at index [0] with value [1] and type [java.lang.Integer] could not be converted or cast to type [int]."); exception = assertThrows(ArgumentAccessException.class, - () -> defaultArgumentsAccessor(1, new Object[] { null }).get(0, int.class)); + () -> defaultArgumentsAccessor(1, new @Nullable Object[] { null }).get(0, int.class)); assertThat(exception.getMessage()).isEqualTo( "Argument at index [0] with value [null] and type [null] could not be converted or cast to type [int]."); } @@ -165,7 +167,8 @@ void size() { assertEquals(5, defaultArgumentsAccessor(1, 'a', 'b', 'c', 'd', 'e').size()); } - private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationIndex, Object... arguments) { + private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationIndex, + @Nullable Object... arguments) { var context = mock(ExtensionContext.class); var classLoader = DefaultArgumentsAccessorTests.class.getClassLoader(); return DefaultArgumentsAccessor.create(context, invocationIndex, classLoader, arguments); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java index 501f4c09a40c..405828e97fbb 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java @@ -29,6 +29,7 @@ import java.util.Locale; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -168,7 +169,7 @@ void delegatesStringToClassWithCustomTypeFromDifferentClassLoaderConversion() th var clazz = (Class) convert(customTypeName, Class.class, testClassLoader); assertThat(clazz).isNotEqualTo(Enigma.class); - assertThat(clazz).isEqualTo(customType); + assertThat(clazz).isNotNull().isEqualTo(customType); assertThat(clazz.getClassLoader()).isSameAs(testClassLoader); verify(underTest).convert(customTypeName, Class.class, testClassLoader); @@ -177,7 +178,7 @@ void delegatesStringToClassWithCustomTypeFromDifferentClassLoaderConversion() th // ------------------------------------------------------------------------- - private void assertConverts(Object input, Class targetClass, Object expectedOutput) { + private void assertConverts(@Nullable Object input, Class targetClass, @Nullable Object expectedOutput) { var result = convert(input, targetClass); assertThat(result) // @@ -187,11 +188,11 @@ private void assertConverts(Object input, Class targetClass, Object expectedO verify(underTest, never()).convert(any(), any(), any(ClassLoader.class)); } - private Object convert(Object input, Class targetClass) { + private @Nullable Object convert(@Nullable Object input, Class targetClass) { return convert(input, targetClass, ClassLoaderUtils.getClassLoader(getClass())); } - private Object convert(Object input, Class targetClass, ClassLoader classLoader) { + private @Nullable Object convert(@Nullable Object input, Class targetClass, ClassLoader classLoader) { return underTest.convert(input, targetClass, classLoader); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java index 2cee9d79f7c9..f2bb6db3f720 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java @@ -30,6 +30,7 @@ import java.time.chrono.ChronoLocalDateTime; import java.time.chrono.ChronoZonedDateTime; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; /** @@ -37,66 +38,77 @@ */ class JavaTimeArgumentConverterTests { + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToChronoLocalDate() { assertThat(convert("01.02.2017", "dd.MM.yyyy", ChronoLocalDate.class)) // .isEqualTo(LocalDate.of(2017, 2, 1)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToChronoLocalDateTime() { assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", ChronoLocalDateTime.class)) // .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToChronoZonedDateTime() { assertThat(convert("01.02.2017 12:34:56.789 Z", "dd.MM.yyyy HH:mm:ss.SSS X", ChronoZonedDateTime.class)) // .isEqualTo(ZonedDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, UTC)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToLocalDate() { assertThat(convert("01.02.2017", "dd.MM.yyyy", LocalDate.class)) // .isEqualTo(LocalDate.of(2017, 2, 1)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToLocalDateTime() { assertThat(convert("01.02.2017 12:34:56.789", "dd.MM.yyyy HH:mm:ss.SSS", LocalDateTime.class)) // .isEqualTo(LocalDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToLocalTime() { assertThat(convert("12:34:56.789", "HH:mm:ss.SSS", LocalTime.class)) // .isEqualTo(LocalTime.of(12, 34, 56, 789_000_000)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToOffsetDateTime() { assertThat(convert("01.02.2017 12:34:56.789 +02", "dd.MM.yyyy HH:mm:ss.SSS X", OffsetDateTime.class)) // .isEqualTo(OffsetDateTime.of(2017, 2, 1, 12, 34, 56, 789_000_000, ZoneOffset.ofHours(2))); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToOffsetTime() { assertThat(convert("12:34:56.789 -02", "HH:mm:ss.SSS X", OffsetTime.class)) // .isEqualTo(OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.ofHours(-2))); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToYear() { assertThat(convert("2017", "yyyy", Year.class)) // .isEqualTo(Year.of(2017)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToYearMonth() { assertThat(convert("03/2017", "MM/yyyy", YearMonth.class)) // .isEqualTo(YearMonth.of(2017, 3)); } + @SuppressWarnings("DataFlowIssue") @Test void convertsStringToZonedDateTime() { assertThat(convert("01.02.2017 12:34:56.789 Europe/Berlin", "dd.MM.yyyy HH:mm:ss.SSS VV", ZonedDateTime.class)) // @@ -125,16 +137,17 @@ void throwsExceptionOnNullParameterWithoutNullable() { /** * @since 5.12 */ + @SuppressWarnings("DataFlowIssue") @Test void convertsNullableParameter() { assertThat(convert(null, "dd.MM.yyyy", true, LocalDate.class)).isNull(); } - private Object convert(Object input, String pattern, Class targetClass) { + private @Nullable Object convert(@Nullable Object input, String pattern, Class targetClass) { return convert(input, pattern, false, targetClass); } - private Object convert(Object input, String pattern, boolean nullable, Class targetClass) { + private @Nullable Object convert(@Nullable Object input, String pattern, boolean nullable, Class targetClass) { var converter = new JavaTimeArgumentConverter(); var annotation = mock(JavaTimeConversionPattern.class); when(annotation.value()).thenReturn(pattern); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java index 599f3d2e9163..dc93874d7d32 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ParameterContext; @@ -47,6 +48,7 @@ class UnitTests { /** * @since 5.8 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -67,7 +69,7 @@ void convertsSourceToTarget() { ); } - private void assertConverts(String input, int expected) { + private void assertConverts(@Nullable String input, int expected) { assertThat(this.converter.convert(input)).isEqualTo(expected); } @@ -168,7 +170,7 @@ private static class StringLengthArgumentConverter extends TypedArgumentConverte } @Override - protected Integer convert(String source) throws ArgumentConversionException { + protected Integer convert(@Nullable String source) throws ArgumentConversionException { return (source != null ? source.length() : 0); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java index 885ba7a77591..4e75630c5d39 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java @@ -38,6 +38,7 @@ protected Stream provideArguments( } }; + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("should throw exception when null annotation is provided to accept method") void shouldThrowExceptionWhenNullAnnotationIsProvidedToAccept() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index bc479af1a62b..6cc030cf447f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -17,6 +17,7 @@ import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -387,7 +388,7 @@ private Stream provideArguments(CsvSource annotation) { } @SuppressWarnings("unchecked") - private static T[] array(T... elements) { + private static @Nullable T[] array(@Nullable T... elements) { return elements; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index 3a7269f4ee0f..06d27ddf5ae7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; @@ -548,7 +549,7 @@ private Stream provideArguments(CsvFileArgumentsProvider provider, Csv } @SuppressWarnings("unchecked") - private static T[] array(T... elements) { + private static @Nullable T[] array(@Nullable T... elements) { return elements; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java index f8a9a9c6c299..9271b3a97141 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java @@ -28,6 +28,7 @@ import java.util.stream.LongStream; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -519,6 +520,7 @@ void test() { // --- Invalid --------------------------------------------------------- + @Nullable static List nullList = null; static Object object = -1; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java index da3a2994bb8e..14e2b6fb9ace 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java @@ -29,6 +29,8 @@ import java.util.stream.LongStream; import java.util.stream.Stream; +import org.jspecify.annotations.NullUnmarked; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -49,6 +51,7 @@ */ class MethodArgumentsProviderTests { + @Nullable private MutableExtensionRegistry extensionRegistry; @Test @@ -729,15 +732,15 @@ private static Object[] array(Object... objects) { return objects; } - private Stream provideArguments(String... factoryMethodNames) { + private Stream<@Nullable Object[]> provideArguments(String... factoryMethodNames) { return provideArguments(TestCase.class, false, factoryMethodNames); } - private Stream provideArguments(Method testMethod, String factoryMethodName) { + private Stream<@Nullable Object[]> provideArguments(Method testMethod, String factoryMethodName) { return provideArguments(TestCase.class, testMethod, false, factoryMethodName); } - private Stream provideArguments(Class testClass, boolean allowNonStaticMethod, + private Stream<@Nullable Object[]> provideArguments(Class testClass, boolean allowNonStaticMethod, String... factoryMethodNames) { // Ensure we have a non-null test method, even if it's not a real test method. @@ -746,8 +749,8 @@ private Stream provideArguments(Class testClass, boolean allowNonSt return provideArguments(testClass, testMethod, allowNonStaticMethod, factoryMethodNames); } - private Stream provideArguments(Class testClass, Method testMethod, boolean allowNonStaticMethod, - String... factoryMethodNames) { + private Stream<@Nullable Object[]> provideArguments(Class testClass, Method testMethod, + boolean allowNonStaticMethod, String... factoryMethodNames) { var methodSource = mock(MethodSource.class); @@ -756,8 +759,7 @@ private Stream provideArguments(Class testClass, Method testMethod, var extensionContext = mock(ExtensionContext.class); when(extensionContext.getTestClass()).thenReturn(Optional.of(testClass)); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); - when(extensionContext.getExecutableInvoker()).thenReturn( - new DefaultExecutableInvoker(extensionContext, extensionRegistry)); + when(extensionContext.getExecutableInvoker()).thenReturn(getExecutableInvoker(extensionContext)); doCallRealMethod().when(extensionContext).getRequiredTestClass(); @@ -772,6 +774,11 @@ private Stream provideArguments(Class testClass, Method testMethod, return provider.provideArguments(mock(), extensionContext).map(Arguments::get); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + private DefaultExecutableInvoker getExecutableInvoker(ExtensionContext extensionContext) { + return new DefaultExecutableInvoker(extensionContext, extensionRegistry); + } + // ------------------------------------------------------------------------- static class DefaultFactoryMethodNameTestCase { @@ -803,6 +810,7 @@ static Stream test(int num) { } } + @NullUnmarked static class MultipleInvalidDefaultFactoriesTestCase { // Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java index 16306451bca4..4d4ea67ecf24 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; @@ -126,10 +127,12 @@ protected Stream provideArguments(ParameterDeclarations par private static class SomeAnnotationBasedArgumentConverter extends AnnotationBasedArgumentConverter { + @Nullable JavaTimeConversionPattern annotation; @Override - protected Object convert(Object source, Class targetType, JavaTimeConversionPattern annotation) { + protected @Nullable Object convert(@Nullable Object source, Class targetType, + JavaTimeConversionPattern annotation) { this.annotation = annotation; return null; } @@ -137,6 +140,7 @@ protected Object convert(Object source, Class targetType, JavaTimeConversionP private static class SomeAnnotationConsumer implements AnnotationConsumer { + @Nullable CsvSource annotation; @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/support/ParameterInfoIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/support/ParameterInfoIntegrationTests.java index 2b62f8d691e3..c1990a02aa4e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/params/support/ParameterInfoIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/support/ParameterInfoIntegrationTests.java @@ -11,6 +11,7 @@ package org.junit.jupiter.params.support; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Nested; @@ -34,7 +35,7 @@ class ParameterInfoIntegrationTests extends AbstractJupiterTestEngineTests { void storesParameterInfoInExtensionContextStoreOnDifferentLevels() { var results = executeTestsForClass(TestCase.class); - results.allEvents().assertStatistics(stats -> stats.started(7).succeeded(7)); + results.allEvents().debug().assertStatistics(stats -> stats.started(7).succeeded(7)); } @ParameterizedClass @@ -102,6 +103,7 @@ public void beforeEach(ExtensionContext parameterizedTestInvocationContext) { private static void assertParameterInfo(ExtensionContext context, String parameterName, int argumentValue) { var parameterInfo = ParameterInfo.get(context); + assertNotNull(parameterInfo); var declaration = parameterInfo.getDeclarations().get(0).orElseThrow(); assertEquals(parameterName, declaration.getParameterName().orElseThrow()); assertEquals(int.class, declaration.getParameterType()); diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt index d0e026669a22..c5da45e59173 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt @@ -65,7 +65,7 @@ class KotlinFailAssertionsTests { } assertMessageEquals(ex, message) val cause = ex.cause - assertMessageContains(cause, throwableCause) + assertMessageContains(cause!!, throwableCause) } @Test @@ -77,7 +77,7 @@ class KotlinFailAssertionsTests { } assertEmptyMessage(ex) val cause = ex.cause - assertMessageContains(cause, throwableCause) + assertMessageContains(cause!!, throwableCause) } @Test @@ -102,7 +102,7 @@ class KotlinFailAssertionsTests { } assertEmptyMessage(ex) val cause = ex.cause - assertMessageContains(cause, throwableCause) + assertMessageContains(cause!!, throwableCause) } @Test diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index dc66c0e7a4dc..629b68107c0b 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -5,6 +5,7 @@ import org.gradle.internal.os.OperatingSystem plugins { id("junitbuild.java-library-conventions") + id("junitbuild.java-nullability-conventions") id("junitbuild.junit4-compatibility") id("junitbuild.testing-conventions") id("junitbuild.jmh-conventions") diff --git a/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java b/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java index 4ad2dce22055..d7901d7aac7d 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java @@ -27,7 +27,9 @@ void successfulTriesCanBeTransformed() throws Exception { var success = Try.success("foo"); assertThat(success.get()).isEqualTo("foo"); + assertThat(success.getNonNull()).isEqualTo("foo"); assertThat(success.getOrThrow(RuntimeException::new)).isEqualTo("foo"); + assertThat(success.getNonNullOrThrow(RuntimeException::new)).isEqualTo("foo"); assertThat(success.toOptional()).contains("foo"); assertThat(success.andThen(v -> { @@ -70,11 +72,14 @@ void failedTriesCanBeTransformed() throws Exception { assertThat(exception.get()).isSameAs(cause); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void successfulTriesCanStoreNull() throws Exception { var success = Try.success(null); assertThat(success.get()).isNull(); + assertThrows(JUnitException.class, success::getNonNull); assertThat(success.getOrThrow(RuntimeException::new)).isNull(); + assertThrows(RuntimeException.class, () -> success.getNonNullOrThrow(RuntimeException::new)); assertThat(success.toOptional()).isEmpty(); } @@ -96,6 +101,7 @@ void triesWithSameContentAreEqual() { assertThat(failure).isEqualTo(Try.failure(cause)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void methodPreconditionsAreChecked() { assertThrows(JUnitException.class, () -> Try.call(null)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java index b882977606a1..67c36c8a7c12 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; @@ -34,6 +35,7 @@ */ class AnnotationSupportTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isAnnotatedPreconditions() { var optional = Optional.of(Probe.class); @@ -57,6 +59,7 @@ void isAnnotatedDelegates() { AnnotationSupport.isAnnotated(element, Override.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotationOnElementPreconditions() { var optional = Optional.of(Probe.class); @@ -81,7 +84,7 @@ void findAnnotationOnElementDelegates() { AnnotationSupport.findAnnotation(element, Override.class)); } - @SuppressWarnings("deprecation") + @SuppressWarnings({ "deprecation", "DataFlowIssue", "NullAway" }) @Test void findAnnotationOnClassWithSearchModePreconditions() { assertPreconditionViolationException("annotationType", @@ -90,6 +93,7 @@ void findAnnotationOnClassWithSearchModePreconditions() { () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (SearchOption) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotationOnClassWithEnclosingInstanceTypesPreconditions() { assertPreconditionViolationException("enclosingInstanceTypes", @@ -120,6 +124,7 @@ void findAnnotationOnClassWithSearchModeDelegates() { AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); } + @NullUnmarked @Test void findAnnotationOnClassWithEnclosingInstanceTypes() { assertThat(AnnotationSupport.findAnnotation(Probe.class, Tag.class, List.of())) // @@ -130,6 +135,7 @@ void findAnnotationOnClassWithEnclosingInstanceTypes() { .contains(Probe.class.getDeclaredAnnotation(Tag.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findPublicAnnotatedFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -148,6 +154,7 @@ void findPublicAnnotatedFieldsDelegates() { AnnotationSupport.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedMethodsPreconditions() { assertPreconditionViolationException("Class", @@ -192,6 +199,7 @@ void findRepeatableAnnotationsDelegates() throws Throwable { assertEquals(expected.toString(), actual.toString(), "expected equal exception toString representation"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findRepeatableAnnotationsPreconditions() { assertPreconditionViolationException("annotationType", @@ -217,6 +225,7 @@ void findAnnotatedFieldsDelegates() { HierarchyTraversalMode.TOP_DOWN)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -268,6 +277,7 @@ void findAnnotatedFieldValuesForStaticFieldsByType() { .containsExactlyInAnyOrder("s1", "s2"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedFieldValuesPreconditions() { assertPreconditionViolationException("instance", diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java index e2b972348253..98e90a516900 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java @@ -24,6 +24,7 @@ */ class ClassSupportTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void nullSafeToStringPreconditions() { Function, ? extends String> mapper = null; diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java index 18332317aa51..bf9957b9a2d6 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java @@ -34,6 +34,7 @@ */ class ModifierSupportTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isPublicPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isPublic((Class) null)); @@ -51,6 +52,7 @@ void isPublicDelegates(Method method) { assertEquals(ReflectionUtils.isPublic(method), ModifierSupport.isPublic(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isPrivatePreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isPrivate((Class) null)); @@ -68,6 +70,7 @@ void isPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isPrivate(method), ModifierSupport.isPrivate(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isNotPrivatePreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotPrivate((Class) null)); @@ -85,6 +88,7 @@ void isNotPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isNotPrivate(method), ModifierSupport.isNotPrivate(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isAbstractPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isAbstract((Class) null)); @@ -102,6 +106,7 @@ void isAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isAbstract(method), ModifierSupport.isAbstract(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isNotAbstractPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotAbstract((Class) null)); @@ -119,6 +124,7 @@ void isNotAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isNotAbstract(method), ModifierSupport.isNotAbstract(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isStaticPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isStatic((Class) null)); @@ -136,6 +142,7 @@ void isStaticDelegates(Method method) { assertEquals(ReflectionUtils.isStatic(method), ModifierSupport.isStatic(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isNotStaticPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotStatic((Class) null)); @@ -153,6 +160,7 @@ void isNotStaticDelegates(Method method) { assertEquals(ReflectionUtils.isNotStatic(method), ModifierSupport.isNotStatic(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isFinalPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isFinal((Class) null)); @@ -170,6 +178,7 @@ void isFinalDelegates(Method method) { assertEquals(ReflectionUtils.isFinal(method), ModifierSupport.isFinal(method)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isNotFinalPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotFinal((Class) null)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java index 11134ab5608e..4aea6556942f 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java @@ -56,6 +56,7 @@ void tryToLoadClassDelegates() { ReflectionSupport.tryToLoadClass("java.nio.Bits")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToLoadClassPreconditions() { assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.tryToLoadClass(null)); @@ -80,6 +81,7 @@ void tryToLoadClassWithExplicitClassLoaderDelegates() { /** * @since 1.10 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToLoadClassWithExplicitClassLoaderPreconditions() { var cl = getClass().getClassLoader(); @@ -108,6 +110,7 @@ List findAllClassesInClasspathRootDelegates() throws Throwable { /** * @since 1.12 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToGetResourcesPreconditions() { assertPreconditionViolationExceptionForString("Resource name", () -> ReflectionSupport.tryToGetResources(null)); @@ -130,6 +133,7 @@ void tryToGetResources() { ReflectionSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllClassesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -163,6 +167,7 @@ List findAllResourcesInClasspathRootDelegates() throws Throwable { /** * @since 1.11 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -194,6 +199,7 @@ List streamAllResourcesInClasspathRootDelegates() throws Throwable /** * @since 1.11 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -210,6 +216,7 @@ void findAllClassesInPackageDelegates() { ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllClassesInPackagePreconditions() { assertPreconditionViolationExceptionForString("basePackageName", @@ -234,6 +241,7 @@ void findAllResourcesInPackageDelegates() { /** * @since 1.11 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllResourcesInPackagePreconditions() { assertPreconditionViolationExceptionForString("basePackageName", @@ -256,6 +264,7 @@ void streamAllResourcesInPackageDelegates() { /** * @since 1.11 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamAllResourcesInPackagePreconditions() { assertPreconditionViolationExceptionForString("basePackageName", @@ -270,6 +279,7 @@ void findAllClassesInModuleDelegates() { ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllClassesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, @@ -293,6 +303,7 @@ void findAllResourcesInModuleDelegates() { /** * @since 1.11 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllResourcesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, @@ -314,6 +325,7 @@ void streamAllResourcesInModuleDelegates() { /** * @since 1.11 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void streamAllResourcesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, @@ -329,6 +341,7 @@ void newInstanceDelegates() { ReflectionSupport.newInstance(String.class, "foo")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void newInstancePreconditions() { assertPreconditionViolationException("Class", () -> ReflectionSupport.newInstance(null)); @@ -345,6 +358,7 @@ void invokeMethodDelegates() throws Exception { ReflectionSupport.invokeMethod(method, null, "true")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void invokeMethodPreconditions() throws Exception { assertPreconditionViolationException("Method", () -> ReflectionSupport.invokeMethod(null, null, "true")); @@ -368,6 +382,7 @@ void findFieldsDelegates() { ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.TOP_DOWN)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -393,6 +408,7 @@ void tryToReadFieldValueDelegates() throws Exception { ReflectionSupport.tryToReadFieldValue(instanceField, this)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToReadFieldValuePreconditions() throws Exception { assertPreconditionViolationException("Field", () -> ReflectionSupport.tryToReadFieldValue(null, this)); @@ -414,6 +430,7 @@ void findMethodDelegates() { ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findMethodPreconditions() { assertPreconditionViolationException("Class", @@ -447,6 +464,7 @@ void findMethodsDelegates() { ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.TOP_DOWN)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findMethodsPreconditions() { assertPreconditionViolationException("Class", @@ -467,6 +485,7 @@ void findNestedClassesDelegates() { ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findNestedClassesPreconditions() { assertPreconditionViolationException("Class", diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/ConversionSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/ConversionSupportTests.java index 626fbe117939..594cbe9f118f 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/ConversionSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/ConversionSupportTests.java @@ -41,6 +41,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -228,12 +229,11 @@ void convertsStringToClassWithCustomTypeFromDifferentClassLoader() throws Except var customType = testClassLoader.loadClass(customTypeName); assertThat(customType.getClassLoader()).isSameAs(testClassLoader); - var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").get(); + var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").orElseThrow(); assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader); var clazz = (Class) convert(customTypeName, Class.class, classLoader(declaringExecutable)); - assertThat(clazz).isNotEqualTo(Enigma.class); - assertThat(clazz).isEqualTo(customType); + assertThat(clazz).isNotNull().isNotEqualTo(Enigma.class).isEqualTo(customType); assertThat(clazz.getClassLoader()).isSameAs(testClassLoader); } } @@ -308,7 +308,7 @@ void convertsStringToUUID() { // ------------------------------------------------------------------------- - private void assertConverts(String input, Class targetClass, Object expectedOutput) { + private void assertConverts(@Nullable String input, Class targetClass, @Nullable Object expectedOutput) { var result = convert(input, targetClass); assertThat(result) // @@ -316,16 +316,16 @@ private void assertConverts(String input, Class targetClass, Object expectedO .isEqualTo(expectedOutput); } - private Object convert(String input, Class targetClass) { + private @Nullable Object convert(@Nullable String input, Class targetClass) { return convert(input, targetClass, classLoader()); } - private Object convert(String input, Class targetClass, ClassLoader classLoader) { + private @Nullable Object convert(@Nullable String input, Class targetClass, ClassLoader classLoader) { return ConversionSupport.convert(input, targetClass, classLoader); } private static ClassLoader classLoader() { - Method declaringExecutable = ReflectionSupport.findMethod(ConversionSupportTests.class, "foo").get(); + Method declaringExecutable = ReflectionSupport.findMethod(ConversionSupportTests.class, "foo").orElseThrow(); return classLoader(declaringExecutable); } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java index 26a2fa53ad98..8f40d780c8a5 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java @@ -441,12 +441,14 @@ void resourcesCanBeRead() throws IOException { } } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void scanForClassesInPackageForNullBasePackage() { assertThrows(PreconditionViolationException.class, () -> classpathScanner.scanForClassesInPackage(null, allClasses)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void scanForResourcesInPackageForNullBasePackage() { assertThrows(PreconditionViolationException.class, @@ -465,6 +467,7 @@ void scanForResourcesInPackageForWhitespaceBasePackage() { () -> classpathScanner.scanForResourcesInPackage(" ", allResources)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void scanForClassesInPackageForNullClassFilter() { assertThrows(PreconditionViolationException.class, @@ -548,6 +551,7 @@ void findAllClassesInClasspathRootWithFilter() throws Exception { assertTrue(classes.contains(DefaultClasspathScannerTests.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllClassesInClasspathRootForNullRoot() { assertThrows(PreconditionViolationException.class, @@ -560,6 +564,7 @@ void findAllClassesInClasspathRootForNonExistingRoot() { () -> classpathScanner.scanForClassesInClasspathRoot(Path.of("does_not_exist").toUri(), allClasses)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAllClassesInClasspathRootForNullClassFilter() { assertThrows(PreconditionViolationException.class, diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java index 20a282e723bf..31c65511af00 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java @@ -41,6 +41,7 @@ import java.util.Optional; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -334,12 +335,14 @@ private void assertExtensionsFound(Class clazz, String... tags) { () -> "Extensions found for class " + clazz.getName()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedMethodsForNullClass() { assertThrows(PreconditionViolationException.class, () -> findAnnotatedMethods(null, Annotation1.class, TOP_DOWN)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedMethodsForNullAnnotationType() { assertThrows(PreconditionViolationException.class, @@ -420,18 +423,21 @@ void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { // === findAnnotatedFields() =============================================== + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedFieldsForNullClass() { assertThrows(PreconditionViolationException.class, () -> findAnnotatedFields(null, Annotation1.class, isStringField, TOP_DOWN)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedFieldsForNullAnnotationType() { assertThrows(PreconditionViolationException.class, () -> findAnnotatedFields(ClassWithAnnotatedFields.class, null, isStringField, TOP_DOWN)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findAnnotatedFieldsForNullPredicate() { assertThrows(PreconditionViolationException.class, @@ -545,18 +551,21 @@ void findAnnotatedFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exce // === findPublicAnnotatedFields() ========================================= + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findPublicAnnotatedFieldsForNullClass() { assertThrows(PreconditionViolationException.class, () -> findPublicAnnotatedFields(null, String.class, Annotation1.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findPublicAnnotatedFieldsForNullFieldType() { assertThrows(PreconditionViolationException.class, () -> findPublicAnnotatedFields(getClass(), null, Annotation1.class)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findPublicAnnotatedFieldsForNullAnnotationType() { assertThrows(PreconditionViolationException.class, @@ -1004,12 +1013,15 @@ static class ClassWithShadowedAnnotatedFields extends ClassWithAnnotatedFields { // ------------------------------------------------------------------------- @Annotation1 + @Nullable private Boolean privateDirectlyAnnotatedField; @Annotation1 + @Nullable public String directlyAnnotatedField; @ComposedAnnotation + @Nullable public Integer metaAnnotatedField; interface InterfaceWithAnnotatedFields { diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java index 3d8a72df921a..562a0fea2499 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java @@ -29,6 +29,7 @@ */ class ClassLoaderUtilsTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getClassLoaderPreconditions() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -100,6 +101,7 @@ void getDefaultClassLoaderWithNullContextClassLoader() { } } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getLocationFromNullFails() { var exception = assertThrows(PreconditionViolationException.class, () -> ClassLoaderUtils.getLocation(null)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java index 749f62161073..12ef9107f8ce 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java @@ -32,6 +32,7 @@ import java.util.stream.LongStream; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -57,6 +58,7 @@ class CollectionUtilsTests { @Nested class OnlyElement { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void nullCollection() { var exception = assertThrows(PreconditionViolationException.class, @@ -89,6 +91,7 @@ void multiElementCollection() { @Nested class FirstElement { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void nullCollection() { var exception = assertThrows(PreconditionViolationException.class, @@ -191,6 +194,7 @@ void isConvertibleToStreamForNull() { assertThat(CollectionUtils.isConvertibleToStream(null)).isFalse(); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void toStreamWithNull() { Exception exception = assertThrows(PreconditionViolationException.class, @@ -368,7 +372,8 @@ void iteratesListElementsInReverseOrder(@ConvertWith(CommaSeparator.class) List< private static class CommaSeparator implements ArgumentConverter { @Override - public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + public Object convert(@Nullable Object source, ParameterContext context) + throws ArgumentConversionException { return source == null ? List.of() : List.of(((String) source).split(",")); } } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java index 798185711c3c..cc30b382ceac 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java @@ -31,8 +31,10 @@ * * @since 1.0 */ +@SuppressWarnings("ThrowableNotThrown") class ExceptionUtilsTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void throwAsUncheckedExceptionWithNullException() { assertThrows(PreconditionViolationException.class, () -> throwAsUncheckedException(null)); @@ -48,6 +50,7 @@ void throwAsUncheckedExceptionWithUncheckedException() { assertThrows(RuntimeException.class, () -> throwAsUncheckedException(new NumberFormatException())); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void readStackTraceForNullThrowable() { assertThrows(PreconditionViolationException.class, () -> readStackTrace(null)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java index ee74043ceb39..42d9938ff5af 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java @@ -26,12 +26,14 @@ */ class FunctionUtilsTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void whereWithNullFunction() { var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(null, o -> true)); assertEquals("function must not be null", exception.getMessage()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void whereWithNullPredicate() { var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(o -> o, null)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java index 801500ccede9..de415b7c4467 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java @@ -33,6 +33,7 @@ */ class PackageUtilsTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getAttributeWithNullType() { var exception = assertThrows(PreconditionViolationException.class, @@ -40,6 +41,7 @@ void getAttributeWithNullType() { assertEquals("type must not be null", exception.getMessage()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getAttributeWithNullFunction() { var exception = assertThrows(PreconditionViolationException.class, @@ -47,6 +49,7 @@ void getAttributeWithNullFunction() { assertEquals("function must not be null", exception.getMessage()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getAttributeWithFunctionReturningNullIsEmpty() { assertFalse(PackageUtils.getAttribute(ValueWrapper.class, p -> null).isPresent()); @@ -54,7 +57,7 @@ void getAttributeWithFunctionReturningNullIsEmpty() { @Test void getAttributeFromDefaultPackageMemberIsEmpty() throws Exception { - var classInDefaultPackage = ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get(); + var classInDefaultPackage = ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").getNonNull(); assertFalse(PackageUtils.getAttribute(classInDefaultPackage, Package::getSpecificationTitle).isPresent()); } @@ -75,6 +78,7 @@ private Executable isPresent(Function function) { return () -> assertTrue(PackageUtils.getAttribute(ValueWrapper.class, function).isPresent()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getAttributeWithNullTypeAndName() { var exception = assertThrows(PreconditionViolationException.class, @@ -82,6 +86,7 @@ void getAttributeWithNullTypeAndName() { assertEquals("type must not be null", exception.getMessage()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getAttributeWithNullName() { var exception = assertThrows(PreconditionViolationException.class, diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 1c391226562b..90a4fd60a4a7 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -59,6 +59,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.fixtures.TrackLogRecords; @@ -113,6 +114,7 @@ void returnsPrimitiveVoid() throws Exception { assertFalse(ReflectionUtils.returnsPrimitiveVoid(clazz.getDeclaredMethod("methodReturningPrimitive"))); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getAllAssignmentCompatibleClassesWithNullClass() { assertThrows(PreconditionViolationException.class, @@ -127,6 +129,7 @@ void getAllAssignmentCompatibleClasses() { assertTrue(superclasses.stream().allMatch(clazz -> clazz.isAssignableFrom(B.class))); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void newInstance() { // @formatter:off @@ -233,6 +236,7 @@ private static void createDirectories(Path... paths) throws IOException { } } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getDeclaredConstructorPreconditions() { // @formatter:off @@ -531,6 +535,7 @@ static void staticMethod() { @Nested class IsClassAssignableToClassTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isAssignableToForNullSourceType() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -545,6 +550,7 @@ void isAssignableToForPrimitiveSourceType() { .withMessage("source type must not be a primitive type"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isAssignableToForNullTargetType() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -601,6 +607,7 @@ void isAssignableTo() { @Nested class IsObjectAssignableToClassTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isAssignableToForNullClass() { assertThrows(PreconditionViolationException.class, @@ -666,6 +673,7 @@ void isAssignableToForNullObjectAndPrimitive() { @Nested class MethodInvocationTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void invokeMethodPreconditions() { // @formatter:off @@ -732,6 +740,7 @@ private void privateMethod() { @Nested class ResourceLoadingTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToGetResourcePreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetResources("")); @@ -773,6 +782,7 @@ void tryToGetResourceWhenResourceNotFound() { @Nested class ClassLoadingTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToLoadClassPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(null)); @@ -939,6 +949,7 @@ private static void assertTryToLoadClass(String name, Class type) { @Nested class FullyQualifiedMethodNameTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getFullyQualifiedMethodNamePreconditions() { // @formatter:off @@ -974,6 +985,7 @@ void getFullyQualifiedMethodNameForMethodWithMultipleParameters() { // @formatter:on } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void parseFullyQualifiedMethodNamePreconditions() { // @formatter:off @@ -1013,6 +1025,7 @@ void parseFullyQualifiedMethodNameForMethodWithMultipleParameters() { @Nested class NestedClassTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findNestedClassesPreconditions() { // @formatter:off @@ -1022,6 +1035,7 @@ void findNestedClassesPreconditions() { // @formatter:on } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isNestedClassPresentPreconditions() { // @formatter:off @@ -1199,6 +1213,7 @@ static class ClassExtendingClassWithNestedClasses extends ClassWithNestedClasses @Nested class MethodUtilitiesTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToGetMethodPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(null, null)); @@ -1223,6 +1238,7 @@ void tryToGetMethod() throws Exception { assertThat(ReflectionUtils.tryToGetMethod(Object.class, "clone", int.class).toOptional()).isEmpty(); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void isMethodPresentPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isMethodPresent(null, m -> true)); @@ -1244,6 +1260,7 @@ void isMethodPresent() { @Nested class FindMethodTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findMethodByParameterTypesPreconditions() { // @formatter:off @@ -1432,6 +1449,7 @@ void methodWithParameterizedMap(Map map) { @Nested class FindMethodsTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void findMethodsPreconditions() { // @formatter:off @@ -1907,6 +1925,7 @@ String specialBaz() { @Nested class ReadFieldTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToReadFieldValueOfNonexistentStaticField() { assertThrows(NoSuchFieldException.class, @@ -1923,6 +1942,7 @@ void tryToReadFieldValueOfNonexistentInstanceField() { () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MySubClass(42)).get()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void tryToReadFieldValueOfExistingStaticField() throws Exception { assertThat(tryToReadFieldValue(MyClass.class, "staticField", null).get()).isEqualTo(42); @@ -1938,7 +1958,7 @@ void tryToReadFieldValueOfExistingInstanceField() throws Exception { assertThat(tryToReadFieldValue(MyClass.class, "instanceField", instance).get()).isEqualTo(42); var field = MyClass.class.getDeclaredField("instanceField"); - assertThat(tryToReadFieldValue(field, instance).get()).isEqualTo(42); + assertThat(tryToReadFieldValue(field, instance).getNonNull()).isEqualTo(42); var exception = assertThrows(PreconditionViolationException.class, () -> tryToReadFieldValue(field, null).get()); @@ -1974,6 +1994,7 @@ void findFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exception { assertThat(fields).containsExactly(nonStaticField); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void readFieldValuesPreconditions() { List fields = new ArrayList<>(); @@ -2090,6 +2111,7 @@ public static class ClassWithFields { @SuppressWarnings("unused") private final String privateStringField = "enigma"; + @Nullable final String nullStringField = null; public final int integerField = 42; diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java index 4707b9d91a95..315fdb472500 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java @@ -27,6 +27,7 @@ import static org.junit.platform.commons.util.StringUtils.replaceIsoControlCharacters; import static org.junit.platform.commons.util.StringUtils.replaceWhitespaceCharacters; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; @@ -49,6 +50,7 @@ void blankness() { // @formatter:on } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void whitespace() { // @formatter:off @@ -72,6 +74,7 @@ void whitespace() { // @formatter:on } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void controlCharacters() { // @formatter:off @@ -95,6 +98,7 @@ void controlCharacters() { // @formatter:on } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void replaceControlCharacters() { assertNull(replaceIsoControlCharacters(null, "")); @@ -108,6 +112,7 @@ void replaceControlCharacters() { assertThrows(PreconditionViolationException.class, () -> replaceIsoControlCharacters("", null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void replaceWhitespaces() { assertNull(replaceWhitespaceCharacters(null, "")); @@ -172,6 +177,7 @@ private void shouldNotContainIsoControlCharacter(String str) { () -> "'%s' should not contain ISO control character".formatted(str)); } + @NullUnmarked private static class ToStringReturnsNull { @Override diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java index c030ba8673de..10d268699f55 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java @@ -16,6 +16,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.NullUnmarked; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; @@ -26,16 +27,19 @@ */ class ToStringBuilderTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void withNullObject() { assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Object) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void withNullClass() { assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Class) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void appendWithIllegalName() { var builder = new ToStringBuilder(""); @@ -141,6 +145,7 @@ void withDemoImplementation() { assertEquals("RoleModel [name = 'Dilbert', age = 42]", roleModel.toString()); } + @NullUnmarked static class RoleModel { String name; diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java index f7fd34632fe5..972496c23fa6 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java @@ -10,9 +10,12 @@ package org.junit.platform.console; +import static java.util.Objects.requireNonNull; + import java.io.PrintWriter; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.platform.console.options.CommandResult; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -25,6 +28,8 @@ class ConsoleLauncherWrapperResult implements TestExecutionSummary { final String out; final String err; final int code; + + @Nullable private final TestExecutionSummary summary; ConsoleLauncherWrapperResult(String[] args, String out, String err, CommandResult result) { @@ -46,114 +51,118 @@ private void checkTestExecutionSummaryState() { @Override public long getTimeStarted() { checkTestExecutionSummaryState(); - return summary.getTimeStarted(); + return requiredSummary().getTimeStarted(); } @Override public long getTimeFinished() { checkTestExecutionSummaryState(); - return summary.getTimeFinished(); + return requiredSummary().getTimeFinished(); } @Override public long getTotalFailureCount() { checkTestExecutionSummaryState(); - return summary.getTotalFailureCount(); + return requiredSummary().getTotalFailureCount(); } @Override public long getContainersFoundCount() { checkTestExecutionSummaryState(); - return summary.getContainersFoundCount(); + return requiredSummary().getContainersFoundCount(); } @Override public long getContainersStartedCount() { checkTestExecutionSummaryState(); - return summary.getContainersStartedCount(); + return requiredSummary().getContainersStartedCount(); } @Override public long getContainersSkippedCount() { checkTestExecutionSummaryState(); - return summary.getContainersSkippedCount(); + return requiredSummary().getContainersSkippedCount(); } @Override public long getContainersAbortedCount() { checkTestExecutionSummaryState(); - return summary.getContainersAbortedCount(); + return requiredSummary().getContainersAbortedCount(); } @Override public long getContainersSucceededCount() { checkTestExecutionSummaryState(); - return summary.getContainersSucceededCount(); + return requiredSummary().getContainersSucceededCount(); } @Override public long getContainersFailedCount() { checkTestExecutionSummaryState(); - return summary.getContainersFailedCount(); + return requiredSummary().getContainersFailedCount(); } @Override public long getTestsFoundCount() { checkTestExecutionSummaryState(); - return summary.getTestsFoundCount(); + return requiredSummary().getTestsFoundCount(); } @Override public long getTestsStartedCount() { checkTestExecutionSummaryState(); - return summary.getTestsStartedCount(); + return requiredSummary().getTestsStartedCount(); } @Override public long getTestsSkippedCount() { checkTestExecutionSummaryState(); - return summary.getTestsSkippedCount(); + return requiredSummary().getTestsSkippedCount(); } @Override public long getTestsAbortedCount() { checkTestExecutionSummaryState(); - return summary.getTestsAbortedCount(); + return requiredSummary().getTestsAbortedCount(); } @Override public long getTestsSucceededCount() { checkTestExecutionSummaryState(); - return summary.getTestsSucceededCount(); + return requiredSummary().getTestsSucceededCount(); } @Override public long getTestsFailedCount() { checkTestExecutionSummaryState(); - return summary.getTestsFailedCount(); + return requiredSummary().getTestsFailedCount(); } @Override public void printTo(PrintWriter writer) { checkTestExecutionSummaryState(); - summary.printTo(writer); + requiredSummary().printTo(writer); } @Override public void printFailuresTo(PrintWriter writer) { checkTestExecutionSummaryState(); - summary.printFailuresTo(writer); + requiredSummary().printFailuresTo(writer); } @Override public void printFailuresTo(PrintWriter writer, int maxStackTraceLines) { checkTestExecutionSummaryState(); - summary.printFailuresTo(writer, maxStackTraceLines); + requiredSummary().printFailuresTo(writer, maxStackTraceLines); } @Override public List getFailures() { checkTestExecutionSummaryState(); - return summary.getFailures(); + return requiredSummary().getFailures(); + } + + private TestExecutionSummary requiredSummary() { + return requireNonNull(summary); } } diff --git a/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java b/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java index f6852850705b..0bb925ef8bb1 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java @@ -29,6 +29,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; +import static org.mockito.Mockito.mock; import java.io.File; import java.io.IOException; @@ -708,12 +709,12 @@ Result parseArgLine(String argLine) throws IOException { abstract Result parseArgLine(String argLine) throws IOException; private static String[] split(String argLine) { - return "".equals(argLine) ? new String[0] : argLine.split("\\s+"); + return argLine.isEmpty() ? new String[0] : argLine.split("\\s+"); } } private static Result parse(String... args) { - ExecuteTestsCommand command = new ExecuteTestsCommand((__, ___) -> null); + ExecuteTestsCommand command = new ExecuteTestsCommand((__, ___) -> mock()); command.parseArgs(args); return new Result(command.toTestDiscoveryOptions(), command.toTestConsoleOutputOptions()); } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/CompositeTestDescriptorVisitorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/CompositeTestDescriptorVisitorTests.java index 88ee3f0ed7aa..675de2bd7002 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/CompositeTestDescriptorVisitorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/CompositeTestDescriptorVisitorTests.java @@ -22,6 +22,7 @@ class CompositeTestDescriptorVisitorTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void checksPreconditions() { assertThrows(PreconditionViolationException.class, Visitor::composite); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java b/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java index 1152290acd19..0a8bf6fae4cf 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java @@ -28,6 +28,7 @@ */ class TestTagTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void validSyntax() { // @formatter:off @@ -65,6 +66,7 @@ void factory() { assertEquals("foo-tag", TestTag.create("\t foo-tag \n").getName()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void factoryPreconditions() { assertSyntaxViolation(null); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java index 47d1913f19e2..bedc9014524f 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java @@ -10,9 +10,9 @@ package org.junit.platform.engine; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -115,7 +115,7 @@ interface ParsingTestTrait { @Test default void parseMalformedUid() { Throwable throwable = assertThrows(JUnitException.class, () -> getFormat().parse("malformed UID")); - assertTrue(throwable.getMessage().contains("malformed UID")); + assertThat(throwable).hasMessageContaining("malformed UID"); } @Test diff --git a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java index 60a907427d34..4a18ec05cf05 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java @@ -123,6 +123,7 @@ void appendingSegmentInstance() { assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void appendingNullIsNotAllowed() { var uniqueId = UniqueId.forEngine(ENGINE_ID); @@ -229,6 +230,7 @@ void additionalSegmentMakesItNotEqual() { @Nested class Prefixing { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void nullIsNotAPrefix() { var id = UniqueId.forEngine(ENGINE_ID); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java index fd14be421e26..158f086fae47 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java @@ -23,6 +23,7 @@ */ class ClassNameFilterTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void includeClassNamePatternsChecksPreconditions() { assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns((String[]) null)) // @@ -85,6 +86,7 @@ void includeClassNamePatternsWithMultiplePatterns() { + secondRegex + "'"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void excludeClassNamePatternsChecksPreconditions() { assertThatThrownBy(() -> ClassNameFilter.excludeClassNamePatterns((String[]) null)) // diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java index 7d649513deaf..fdc03244c95a 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java @@ -70,6 +70,7 @@ class DiscoverySelectorsTests { @Nested class SelectUriTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectUriByName() { assertViolatesPrecondition(() -> selectUri((String) null)); @@ -82,6 +83,7 @@ void selectUriByName() { assertEquals(uri, selector.getUri().toString()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectUriByURI() { assertViolatesPrecondition(() -> selectUri((URI) null)); @@ -107,6 +109,7 @@ void parseUriSelector() { .isEqualTo(URI.create("https://junit.org")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectFileByName() { assertViolatesPrecondition(() -> selectFile((String) null)); @@ -120,6 +123,7 @@ void selectFileByName() { assertEquals(Path.of(path), selector.getPath()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectFileByNameAndPosition() { var filePosition = FilePosition.from(12, 34); @@ -135,6 +139,7 @@ void selectFileByNameAndPosition() { assertEquals(filePosition, selector.getPosition().orElseThrow()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectFileByFileReference() throws Exception { assertViolatesPrecondition(() -> selectFile((File) null)); @@ -151,6 +156,7 @@ void selectFileByFileReference() throws Exception { assertEquals(Path.of(path), selector.getPath()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectFileByFileReferenceAndPosition() throws Exception { var filePosition = FilePosition.from(12, 34); @@ -227,6 +233,7 @@ void parseFileSelectorWithAbsolutePathAndFilePosition() { Optional.of(filePosition)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectDirectoryByName() { assertViolatesPrecondition(() -> selectDirectory((String) null)); @@ -240,6 +247,7 @@ void selectDirectoryByName() { assertEquals(Path.of(path), selector.getPath()); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectDirectoryByFileReference() throws Exception { assertViolatesPrecondition(() -> selectDirectory((File) null)); @@ -287,6 +295,7 @@ void parseDirectorySelectorWithAbsolutePath() { .containsExactly(path, new File(path), Path.of(path)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectClasspathResourcesPreconditions() { assertViolatesPrecondition(() -> selectClasspathResource((String) null)); @@ -330,6 +339,7 @@ void getMissingClasspathResources() { assertViolatesPrecondition(selector::getClasspathResources); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectClasspathResourcesWithFilePosition() { var filePosition = FilePosition.from(12, 34); @@ -397,7 +407,7 @@ public String getName() { @Override public URI getUri() { - return null; + throw new UnsupportedOperationException(); } } } @@ -420,6 +430,7 @@ void parseModuleByName() { .isEqualTo("java.base"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectModuleByNamePreconditions() { assertViolatesPrecondition(() -> selectModule(null)); @@ -434,6 +445,7 @@ void selectModulesByNames() { assertThat(names).containsExactlyInAnyOrder("b", "a"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectModulesByNamesPreconditions() { assertViolatesPrecondition(() -> selectModules(null)); @@ -533,6 +545,7 @@ void selectClassByNameWithExplicitClassLoader() throws Exception { @Nested class SelectMethodTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectMethod(className, methodName)") void selectMethodByClassNameAndMethodNamePreconditions() { @@ -544,6 +557,7 @@ void selectMethodByClassNameAndMethodNamePreconditions() { assertViolatesPrecondition(() -> selectMethod(" ", "method")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypeNames)") void selectMethodByClassNameMethodNameAndParameterTypeNamesPreconditions() { @@ -556,6 +570,7 @@ void selectMethodByClassNameMethodNameAndParameterTypeNamesPreconditions() { assertViolatesPrecondition(() -> selectMethod("TestClass", "method", (String) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypes)") void selectMethodByClassNameMethodNameAndParameterTypesPreconditions() { @@ -569,6 +584,7 @@ void selectMethodByClassNameMethodNameAndParameterTypesPreconditions() { assertViolatesPrecondition(() -> selectMethod("TestClass", "method", new Class[] { int.class, null })); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectMethod(class, methodName)") void selectMethodByClassAndMethodNamePreconditions() { @@ -578,6 +594,7 @@ void selectMethodByClassAndMethodNamePreconditions() { assertViolatesPrecondition(() -> selectMethod((Class) null, "method")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectMethod(class, methodName, parameterTypeNames)") void selectMethodByClassMethodNameAndParameterTypeNamesPreconditions() { @@ -588,6 +605,7 @@ void selectMethodByClassMethodNameAndParameterTypeNamesPreconditions() { assertViolatesPrecondition(() -> selectMethod(testClass(), "method", (String) null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectMethod(class, method)") void selectMethodByClassAndMethodPreconditions() { @@ -1131,6 +1149,7 @@ void selectDoubleNestedClassByClassNames() { assertThat(parseIdentifier(selector)).isEqualTo(selector); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void selectNestedClassPreconditions() { assertViolatesPrecondition(() -> selectNestedClass(null, "ClassName")); @@ -1298,6 +1317,7 @@ void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Excepti assertThat(parseIdentifier(selector)).isEqualTo(selector); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName)") void selectNestedMethodByEnclosingClassNamesAndMethodNamePreconditions() { @@ -1309,6 +1329,7 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNamePreconditions() { assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypeNames)") void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesPreconditions() { @@ -1325,6 +1346,7 @@ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesPreco /** * @since 1.10 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypes)") void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypesPreconditions() { @@ -1343,6 +1365,7 @@ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypesPrecondit /** * @since 1.10 */ + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClasses, nestedClass, methodName, parameterTypes)") void selectNestedMethodByEnclosingClassesClassMethodNameAndParameterTypesPreconditions() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java index b93ba77a34c9..fb52e9c9858b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java @@ -23,6 +23,7 @@ */ class PackageNameFilterTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void includePackageChecksPreconditions() { assertThatThrownBy(() -> PackageNameFilter.includePackageNames((String[]) null)) // @@ -74,6 +75,7 @@ void includePackageWithMultiplePackages() { + includedPackage2 + "'"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void excludePackageChecksPreconditions() { assertThatThrownBy(() -> PackageNameFilter.excludePackageNames((String[]) null)) // diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java index fe064e60e58b..c2f4fa7f0fb2 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java @@ -37,6 +37,7 @@ class PrefixedConfigurationParametersTests { @Mock private ConfigurationParameters delegate; + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThrows(PreconditionViolationException.class, () -> new PrefixedConfigurationParameters(null, "example.")); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java index 078c9f629c76..e1ee9cd4e0e3 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java @@ -39,6 +39,7 @@ Stream createSerializableInstances() { ); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThrows(PreconditionViolationException.class, () -> ClassSource.from((String) null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java index 9141a47fd5bb..a84a471d9b92 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java @@ -38,6 +38,7 @@ Stream createSerializableInstances() { return Stream.of(ClasspathResourceSource.from(FOO_RESOURCE)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThrows(PreconditionViolationException.class, () -> ClasspathResourceSource.from((String) null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java index 2702e480ae33..f09f0efbcaa3 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java @@ -37,6 +37,7 @@ Stream createSerializableInstances() { return Stream.of(CompositeTestSource.from(sources)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void createCompositeTestSourceFromNullList() { assertThrows(PreconditionViolationException.class, () -> CompositeTestSource.from(null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java index 03f5edd70f6b..f68fc5e6d8c3 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java @@ -33,6 +33,7 @@ Stream createSerializableInstances() { return Stream.of(new DefaultUriSource(URI.create("sample://instance"))); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void nullSourceUriYieldsException() { assertThrows(PreconditionViolationException.class, () -> new DefaultUriSource(null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java index 307193a02774..9f6723b85e31 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java @@ -34,6 +34,7 @@ Stream createSerializableInstances() { FileSource.from(new File("file.and.position"), FilePosition.from(42, 23))); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void nullSourceFileOrDirectoryYieldsException() { assertThrows(PreconditionViolationException.class, () -> FileSource.from(null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java index 4f97624c7ce6..df9106349d16 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java @@ -57,6 +57,7 @@ void equalsAndHashCodeForMethodSource() throws Exception { assertEqualsAndHashCode(MethodSource.from(method1), MethodSource.from(method1), MethodSource.from(method2)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void instantiatingWithNullNamesShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", null)); @@ -75,11 +76,13 @@ void instantiatingWithBlankNamesShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, () -> MethodSource.from(" ", "foo")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void instantiationWithNullMethodShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, () -> MethodSource.from(null)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void instantiationWithNullClassOrMethodShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java index 8da7f16a8545..f964343c4c2d 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java @@ -32,6 +32,7 @@ Stream createSerializableInstances() { return Stream.of(PackageSource.from("package.source")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void packageSourceFromNullPackageName() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from((String) null)); @@ -42,6 +43,7 @@ void packageSourceFromEmptyPackageName() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from(" ")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void packageSourceFromNullPackageReference() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from((Package) null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorTests.java index 2d7d96308ecd..b1b0591d5b3a 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorTests.java @@ -15,6 +15,7 @@ import java.util.Optional; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.TestTag; @@ -57,7 +58,7 @@ public Optional getParent() { } @Override - public void setParent(TestDescriptor parent) { + public void setParent(@Nullable TestDescriptor parent) { throw new UnsupportedOperationException("Not implemented"); } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java index 9a55301ab6af..d4683aad6d7b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java @@ -49,6 +49,7 @@ void fixedStrategyCreatesValidConfiguration() { assertThat(configuration.getMinimumRunnable()).isEqualTo(42); assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + 42); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); } @@ -62,6 +63,7 @@ void fixedSaturateStrategyCreatesValidConfiguration() { var configuration = strategy.createConfiguration(configParams); assertThat(configuration.getParallelism()).isEqualTo(42); assertThat(configuration.getMaxPoolSize()).isEqualTo(42); + assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isFalse(); } @@ -78,6 +80,7 @@ void dynamicStrategyCreatesValidConfiguration() { assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2); assertThat(configuration.getMaxPoolSize()).isEqualTo(256 + (availableProcessors * 2)); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isTrue(); } @@ -96,6 +99,7 @@ void dynamicSaturateStrategyCreatesValidConfiguration() { assertThat(configuration.getMinimumRunnable()).isEqualTo(availableProcessors * 2); assertThat(configuration.getMaxPoolSize()).isEqualTo(availableProcessors * 6); assertThat(configuration.getKeepAliveSeconds()).isEqualTo(30); + assertThat(configuration.getSaturatePredicate()).isNotNull(); assertThat(configuration.getSaturatePredicate().test(null)).isFalse(); } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java index 59dfbba0f789..a31f31598a4a 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java @@ -33,6 +33,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.function.Executable; @@ -105,6 +106,7 @@ static List incompatibleLockCombinations() { ); } + @SuppressWarnings("NullAway") @ParameterizedTest @MethodSource("incompatibleLockCombinations") void defersTasksWithIncompatibleLocks(Set initialResources, @@ -166,6 +168,7 @@ static List compatibleLockCombinations() { ); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @ParameterizedTest @MethodSource("compatibleLockCombinations") void canWorkStealTaskWithCompatibleLocks(Set initialResources, @@ -198,6 +201,8 @@ void defersTasksWithIncompatibleLocksOnMultipleLevels() throws Throwable { var deferred = new ConcurrentHashMap(); var deferredTasks = new CopyOnWriteArrayList(); + + @SuppressWarnings("NullAway") TaskEventListener taskEventListener = testTask -> { deferredTasks.add(testTask); deferred.get(testTask).countDown(); @@ -305,9 +310,11 @@ static final class DummyTestTask implements TestTask { private final ResourceLock resourceLock; private final Executable action; + @Nullable private volatile String threadName; + private final CountDownLatch started = new CountDownLatch(1); - private final CompletableFuture completion = new CompletableFuture<>(); + private final CompletableFuture<@Nullable Void> completion = new CompletableFuture<>(); DummyTestTask(String identifier, ResourceLock resourceLock, Executable action) { this.identifier = identifier; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java index 81b1797c8b2b..cf5b418d2a52 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java @@ -189,6 +189,7 @@ void getWithTypeSafety() { assertEquals(value, requiredTypeValue); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; @@ -239,6 +240,7 @@ void getOrComputeIfAbsentWithTypeSafety() { assertEquals(value, computedValue); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; @@ -285,6 +287,7 @@ void removeWithTypeSafety() { assertNull(store.get(namespace, key)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void removeWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java index 57f716bb0e5e..af47b9fabd3d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java @@ -39,6 +39,7 @@ class MethodFilterTests { private static final TestDescriptor CLASS2_TEST1 = methodTestDescriptor("class2", Class2.class, "test1"); private static final TestDescriptor CLASS2_TEST2 = methodTestDescriptor("class2", Class2.class, "test2"); + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void includeMethodNamePatternsChecksPreconditions() { assertThatThrownBy(() -> includeMethodNamePatterns((String[]) null)) // @@ -86,6 +87,7 @@ void includeMultipleMethodNamePatterns() { firstRegex, secondRegex)); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void excludeMethodNamePatternsChecksPreconditions() { assertThatThrownBy(() -> excludeMethodNamePatterns((String[]) null)) // diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java index d81e7879ef0b..734bbcdb0a34 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; @@ -60,7 +61,8 @@ void includeTagsWithInvalidSyntax() { // @formatter:on } - private void assertSyntaxViolationForIncludes(String tag) { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + private void assertSyntaxViolationForIncludes(@Nullable String tag) { var exception = assertThrows(PreconditionViolationException.class, () -> includeTags(tag)); assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); } @@ -77,7 +79,8 @@ void excludeTagsWithInvalidSyntax() { // @formatter:on } - private void assertSyntaxViolationForExcludes(String tag) { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + private void assertSyntaxViolationForExcludes(@Nullable String tag) { var exception = assertThrows(PreconditionViolationException.class, () -> excludeTags(tag)); assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java index cc530eed7924..adf70020ca02 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java @@ -129,6 +129,7 @@ void discoverEmptyTestPlanWithEngineWithoutAnyTests() { void discoverTestPlanForEngineThatReturnsNullForItsRootDescriptor() { TestEngine engine = new TestEngineStub("some-engine-id") { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return null; @@ -149,6 +150,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId void discoverErrorTestDescriptorForEngineThatThrowsInDiscoveryPhase(Class throwableClass) { TestEngine engine = new TestEngineStub("my-engine-id") { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { try { @@ -427,7 +429,7 @@ void withoutConfigurationParameters_LauncherPassesEmptyConfigurationParametersIn var launcher = createLauncher(engine); launcher.execute(request().build()); - var configurationParameters = engine.requestForExecution.getConfigurationParameters(); + var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); assertThat(configurationParameters.get("key")).isNotPresent(); } @@ -438,7 +440,7 @@ void withConfigurationParameters_LauncherPassesPopulatedConfigurationParametersI var launcher = createLauncher(engine); launcher.execute(request().configurationParameter("key", "value").build()); - var configurationParameters = engine.requestForExecution.getConfigurationParameters(); + var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); assertThat(configurationParameters.get("key")).isPresent(); assertThat(configurationParameters.get("key")).contains("value"); } @@ -453,7 +455,7 @@ void withoutConfigurationParameters_LookupFallsBackToSystemProperty() { var launcher = createLauncher(engine); launcher.execute(request().build()); - var configurationParameters = engine.requestForExecution.getConfigurationParameters(); + var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); var optionalFoo = configurationParameters.get(FOO); assertTrue(optionalFoo.isPresent(), "foo should have been picked up via system property"); assertEquals(BAR, optionalFoo.get(), "foo property"); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java index d8a29dc6e105..f1c05f80fc37 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java @@ -16,6 +16,7 @@ import java.util.Map; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.TestDescriptor; @@ -65,7 +66,10 @@ void nothing() { static class MockTestExecutionListener implements TestExecutionListener { + @Nullable public TestIdentifier testIdentifier; + + @Nullable public ReportEntry entry; @Override diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java index c3b518dcd3b2..7b3a92d90e5b 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java @@ -30,6 +30,7 @@ */ class LauncherConfigTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThrows(PreconditionViolationException.class, diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java index b2608e60fcba..05a74b2bb9ee 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java @@ -59,6 +59,7 @@ void reset() { System.clearProperty(KEY); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void constructorPreconditions() { assertThrows(PreconditionViolationException.class, () -> fromMap(null)); @@ -67,6 +68,7 @@ void constructorPreconditions() { assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), " ")); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void getPreconditions() { ConfigurationParameters configParams = fromMap(Map.of()); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java index c6f4a74cd64c..99c2f2ecc599 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.core; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -62,6 +63,7 @@ */ class LauncherFactoryTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThrows(PreconditionViolationException.class, () -> LauncherFactory.create(null)); @@ -328,7 +330,7 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult } }); - assertThat(result.get().getThrowable().orElseThrow()) // + assertThat(requireNonNull(result.get()).getThrowable().orElseThrow()) // .hasRootCauseMessage("from execution") // .hasStackTraceContaining(TestLauncherInterceptor1.class.getName() + ".intercept(") // .hasStackTraceContaining(TestLauncherInterceptor2.class.getName() + ".intercept("); @@ -577,6 +579,7 @@ public void launcherSessionClosed(LauncherSession session) { .getStore() // .get(Namespace.GLOBAL, "sessionResource", CloseTrackingResource.class); + assertThat(sessionResource).isNotNull(); assertThat(sessionResource.isClosed()).isFalse(); } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java index 1c3a08783764..9f7052c2fd06 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java @@ -13,14 +13,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.List; + import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; public class ListenerRegistryTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void registerWithNullArray() { - var registry = ListenerRegistry.create(l -> l.getFirst()); + var registry = ListenerRegistry.create(List::getFirst); var exception = assertThrows(PreconditionViolationException.class, () -> registry.addAll((Object[]) null)); @@ -29,16 +32,17 @@ void registerWithNullArray() { @Test void registerWithEmptyArray() { - var registry = ListenerRegistry.create(l -> l.getFirst()); + var registry = ListenerRegistry.create(List::getFirst); var exception = assertThrows(PreconditionViolationException.class, registry::addAll); assertThat(exception).hasMessageContaining("listeners array must not be null or empty"); } + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void registerWithArrayContainingNullElements() { - var registry = ListenerRegistry.create(l -> l.getFirst()); + var registry = ListenerRegistry.create(List::getFirst); var exception = assertThrows(PreconditionViolationException.class, () -> registry.addAll(new Object[] { null })); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java index 2b5f35ca86bb..cb8aa8856663 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java @@ -20,6 +20,7 @@ import java.io.PrintStream; import java.util.stream.IntStream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.Test; @@ -32,6 +33,7 @@ class StreamInterceptorTests { PrintStream targetStream = new PrintStream(originalOut); @AutoClose + @Nullable StreamInterceptor streamInterceptor; @Test diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java index dd916c324636..35fd8cc2692d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.listeners; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -44,6 +45,7 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; @@ -214,6 +216,7 @@ private List executeTests(Map configurationParameters, C .build(); LauncherFactory.create().execute(request, new TestExecutionListener() { + @Nullable private TestPlan testPlan; @Override @@ -227,7 +230,8 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) { uniqueIds.add(testIdentifier.getUniqueId()); } else { - this.testPlan.getChildren(testIdentifier).forEach(child -> executionSkipped(child, reason)); + requireNonNull(this.testPlan).getChildren(testIdentifier).forEach( + child -> executionSkipped(child, reason)); } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java index 648d624cac3c..1d5bcd3cb0dc 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java @@ -15,6 +15,7 @@ import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -24,11 +25,6 @@ class ParserErrorTests { private final Parser parser = new Parser(); - @Test - void cantParseExpressionFromNull() { - assertThat(parseErrorFromParsing(null)).contains("empty tag expression"); - } - @Test void emptyExpression() { assertThat(parseErrorFromParsing("")).contains("empty tag expression"); @@ -94,7 +90,7 @@ private static Stream data() { // @formatter:on } - private String parseErrorFromParsing(String tagExpression) { + private @Nullable String parseErrorFromParsing(String tagExpression) { try { var parseResult = parser.parse(tagExpression); parseResult.tagExpressionOrThrow(RuntimeException::new); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java index 37cfb09786e6..f305c8b26362 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; class TokenizerTests { @@ -85,11 +86,11 @@ private Stream rawStringsExtractedFrom(String expression) { return tokensExtractedFrom(expression).map(token -> token.rawString); } - private List tokenStringsExtractedFrom(String expression) { + private List tokenStringsExtractedFrom(@Nullable String expression) { return tokensExtractedFrom(expression).map(Token::string).toList(); } - private Stream tokensExtractedFrom(String expression) { + private Stream tokensExtractedFrom(@Nullable String expression) { return new Tokenizer().tokenize(expression).stream(); } } diff --git a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java index 125a8c2a8e81..efa86382c9bc 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java @@ -691,7 +691,7 @@ public Optional getBoolean(String key) { @Override public Set keySet() { - return null; + return Set.of(); } } diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java index 2d519bede3b6..bd8009c8f4ff 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EventsTests.java @@ -10,9 +10,9 @@ package org.junit.platform.testkit.engine; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; @@ -171,7 +171,7 @@ void assertEventsMatchLooselyInOrderWithAllEventsInWrongOrderFails() { ); var error = assertThrows(AssertionFailedError.class, willFail); - assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); + assertThat(error).hasMessageContaining("Conditions are not in the correct order."); } @Test @@ -201,7 +201,7 @@ void assertEventsMatchLooselyInOrderWithASubsetInWrongOrderFails() { ); var error = assertThrows(AssertionFailedError.class, willFail); - assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); + assertThat(error).hasMessageContaining("Conditions are not in the correct order."); } @Test @@ -266,7 +266,7 @@ void assertEventsMatchLooselyInOrderWithSecondAndLastEventInBadOrderFails() { ); var error = assertThrows(AssertionFailedError.class, willFail); - assertTrue(error.getMessage().contains("Conditions are not in the correct order.")); + assertThat(error).hasMessageContaining("Conditions are not in the correct order."); } @Test @@ -281,7 +281,7 @@ void assertEventsMatchLooselyInOrderWithTooManyEventsFails() { event(engine(), finishedSuccessfully())); var error = assertThrows(AssertionError.class, willFail); - assertTrue(error.getMessage().endsWith("to be less than or equal to 4 but was 6")); + assertThat(error).hasMessageEndingWith("to be less than or equal to 4 but was 6"); } } diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java index d109969008b1..dcece1a3a020 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java @@ -41,6 +41,7 @@ */ class NestedContainerEventConditionTests { + @SuppressWarnings({ "DataFlowIssue", "NullAway" }) @Test void preconditions() { assertThatExceptionOfType(PreconditionViolationException.class)// diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index b37a2a1318a9..b321d09be1da 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -7,6 +7,7 @@ import java.time.Duration plugins { id("junitbuild.build-parameters") + id("junitbuild.java-nullability-conventions") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") } @@ -59,6 +60,7 @@ dependencies { thirdPartyJars(libs.assertj) thirdPartyJars(libs.apiguardian) thirdPartyJars(libs.hamcrest) + thirdPartyJars(libs.jspecify) thirdPartyJars(libs.opentest4j) thirdPartyJars(libs.openTestReporting.tooling.spi) thirdPartyJars(libs.jimfs) @@ -119,6 +121,10 @@ val archUnit by testing.suites.registering(JvmTestSuite::class) { implementation(libs.apiguardian) { because("we validate that public classes are annotated") } + implementation(libs.jspecify) { + because("we validate that packages are annotated") + } + implementation(libs.assertj) runtimeOnly.bundle(libs.bundles.log4j) val modularProjects: List by rootProject modularProjects.forEach { diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt index 673304bda14d..7fa1fa9cf145 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt @@ -9,6 +9,7 @@ exports org.junit.jupiter.api.parallel requires java.base mandated requires kotlin.stdlib static requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.platform.commons transitive requires org.opentest4j transitive qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt index 699fe7363234..65a155299cf8 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-engine.expected.txt @@ -1,6 +1,7 @@ org.junit.jupiter.engine@${version} jar:file:.+/junit-jupiter-engine-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static +requires org.jspecify static requires org.junit.jupiter.api requires org.junit.platform.commons requires org.junit.platform.engine diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt index 4012e4139344..933d05fecf74 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-migrationsupport.expected.txt @@ -7,5 +7,6 @@ exports org.junit.jupiter.migrationsupport.rules.member requires java.base mandated requires junit transitive requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.jupiter.api transitive requires org.junit.platform.commons diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt index c23606354350..95e8e08ab7f7 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-params.expected.txt @@ -6,6 +6,7 @@ exports org.junit.jupiter.params.provider exports org.junit.jupiter.params.support requires java.base mandated requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.jupiter.api transitive requires org.junit.platform.commons transitive qualified opens org.junit.jupiter.params to org.junit.platform.commons diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index 0022a3df70bc..79178a9ec365 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -12,6 +12,7 @@ requires kotlin.reflect static requires kotlin.stdlib static requires kotlinx.coroutines.core static requires org.apiguardian.api static transitive +requires org.jspecify static transitive uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.jfr org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt index 031b331cc0b3..01086247b6b6 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt @@ -1,6 +1,7 @@ org.junit.platform.console@${version} jar:file:.+/junit-platform-console-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static +requires org.jspecify static requires org.junit.platform.commons requires org.junit.platform.engine requires org.junit.platform.launcher diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt index 7849ee4adb77..cc88b0d84c7e 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-engine.expected.txt @@ -9,6 +9,7 @@ exports org.junit.platform.engine.support.hierarchical exports org.junit.platform.engine.support.store requires java.base mandated requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.platform.commons transitive requires org.opentest4j transitive uses org.junit.platform.engine.discovery.DiscoverySelectorIdentifierParser diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt index bc727ca43cf6..b8f85f92a64e 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-jfr.expected.txt @@ -2,6 +2,7 @@ org.junit.platform.jfr@${version} jar:file:.+/junit-platform-jfr-\d.+\.jar..modu requires java.base mandated requires jdk.jfr requires org.apiguardian.api static +requires org.jspecify static requires org.junit.platform.engine requires org.junit.platform.launcher provides org.junit.platform.launcher.LauncherDiscoveryListener with org.junit.platform.jfr.FlightRecordingDiscoveryListener diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index e8f16dfa1f46..71aff5462347 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -6,6 +6,7 @@ exports org.junit.platform.launcher.listeners.discovery requires java.base mandated requires java.logging transitive requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.platform.commons transitive requires org.junit.platform.engine transitive uses org.junit.platform.engine.TestEngine diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt index 6bd9a8b227c3..c9f06e55f62b 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt @@ -5,6 +5,7 @@ exports org.junit.platform.reporting.open.xml requires java.base mandated requires java.xml requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.platform.commons requires org.junit.platform.engine transitive requires org.junit.platform.launcher transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt index 77c942f97f87..5b5b0b36a9b3 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-api.expected.txt @@ -2,4 +2,5 @@ org.junit.platform.suite.api@${version} jar:file:.+/junit-platform-suite-api-\d. exports org.junit.platform.suite.api requires java.base mandated requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.platform.commons transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt index 0d25ab3dc62b..1d5c6b9f584c 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt @@ -1,6 +1,7 @@ org.junit.platform.suite.commons@${version} jar:file:.+/junit-platform-suite-commons-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static transitive +requires org.jspecify static requires org.junit.platform.commons requires org.junit.platform.engine requires org.junit.platform.launcher transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt index cca1aba09043..d655ec351ed3 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-engine.expected.txt @@ -1,6 +1,7 @@ org.junit.platform.suite.engine@${version} jar:file:.+/junit-platform-suite-engine-\d.+\.jar..module-info\.class requires java.base mandated requires org.apiguardian.api static +requires org.jspecify static requires org.junit.platform.commons requires org.junit.platform.engine requires org.junit.platform.launcher diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt index da43a9f5bd2c..008e2f1caf0a 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-testkit.expected.txt @@ -3,6 +3,7 @@ exports org.junit.platform.testkit.engine requires java.base mandated requires org.apiguardian.api static transitive requires org.assertj.core transitive +requires org.jspecify static requires org.junit.platform.commons requires org.junit.platform.engine transitive requires org.junit.platform.launcher transitive diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt index f3b130c0aa8d..79a358e40a59 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-vintage-engine.expected.txt @@ -2,6 +2,7 @@ org.junit.vintage.engine@${version} jar:file:.+/junit-vintage-engine-\d.+\.jar.. requires java.base mandated requires junit requires org.apiguardian.api static +requires org.jspecify static requires org.junit.platform.engine provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine contains org.junit.vintage.engine diff --git a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java index c54641f46604..47dad8ed873e 100644 --- a/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/archUnit/java/platform/tooling/support/tests/ArchUnitTests.java @@ -15,7 +15,6 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.TOP_LEVEL_CLASSES; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAnyPackage; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameEndingWith; import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC; @@ -27,6 +26,7 @@ import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.annotation.Annotation; @@ -35,10 +35,13 @@ import java.lang.annotation.Target; import java.util.Arrays; import java.util.function.BiPredicate; +import java.util.stream.Stream; import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaPackage; +import com.tngtech.archunit.core.domain.PackageMatcher; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchCondition; @@ -46,6 +49,7 @@ import com.tngtech.archunit.library.GeneralCodingRules; import org.apiguardian.api.API; +import org.jspecify.annotations.NullMarked; @AnalyzeClasses(packages = { "org.junit.platform", "org.junit.jupiter", "org.junit.vintage" }) class ArchUnitTests { @@ -64,7 +68,7 @@ class ArchUnitTests { .and(not(describe("are Kotlin SAM type implementations", simpleName("")))) // .and(not(describe("are Kotlin-generated classes that contain only top-level functions", simpleNameEndingWith("Kt")))) // - .and(not(describe("are shadowed", resideInAnyPackage("..shadow..")))) // + .and(not(describe("are shadowed", resideInAPackage("..shadow..")))) // .should().beAnnotatedWith(API.class); @SuppressWarnings("unused") @@ -76,6 +80,27 @@ class ArchUnitTests { .should(haveContainerAnnotationWithSameRetentionPolicy()) // .andShould(haveContainerAnnotationWithSameTargetTypes()); + @ArchTest + void packagesShouldBeNullMarked(JavaClasses classes) { + var exclusions = Stream.of( // + "..shadow.." // + ).map(PackageMatcher::of).toList(); + + var subpackages = Stream.of("org.junit.platform", "org.junit.jupiter", "org.junit.vintage") // + .map(classes::getPackage) // + .flatMap(rootPackage -> rootPackage.getSubpackagesInTree().stream()) // + .filter(pkg -> exclusions.stream().noneMatch(it -> it.matches(pkg.getName()))) // + .filter(pkg -> !pkg.getClasses().isEmpty()) // + .toList(); + assertThat(subpackages).isNotEmpty(); + + var violations = subpackages.stream() // + .filter(pkg -> !pkg.isAnnotatedWith(NullMarked.class)) // + .map(JavaPackage::getName) // + .sorted(); + assertThat(violations).describedAs("The following packages are missing the @NullMarked annotation").isEmpty(); + } + @ArchTest void allAreIn(JavaClasses classes) { // about 928 classes found in all jars diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index 6451f5e8d1fd..7882fc19847b 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -89,6 +89,7 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex ThirdPartyJars.copy(lib, "net.bytebuddy", "byte-buddy"); ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); ThirdPartyJars.copy(lib, "org.hamcrest", "hamcrest"); + ThirdPartyJars.copy(lib, "org.jspecify", "jspecify"); ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); ThirdPartyJars.copy(lib, "com.google.jimfs", "jimfs"); @@ -172,6 +173,7 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, "lib/guava-.+\\.jar", // "lib/hamcrest-.+\\.jar", // "lib/jimfs-.+\\.jar", // + "lib/jspecify-.+\\.jar", // "lib/junit-.+\\.jar", // ">> ALL JUNIT 5 JARS >>", // "lib/opentest4j-.+\\.jar", // diff --git a/settings.gradle.kts b/settings.gradle.kts index 3cb5c303cc6f..3d3cd294178d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,6 +15,12 @@ plugins { dependencyResolutionManagement { repositories { mavenCentral() + maven(url = "https://central.sonatype.com/repository/maven-snapshots") { + mavenContent { + snapshotsOnly() + includeGroup("org.opentest4j.reporting") + } + } } }