From c03548007ee959df5242ac3645acb819bcccfff4 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 27 Jun 2025 16:34:27 +0200 Subject: [PATCH 001/162] Back to snapshots for further development --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3b591bcde84e..000b32c34896 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 6.0.0-M1 +version = 6.0.0-SNAPSHOT # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError From d3ffd137f836ed1645c0cd0b2aba4876a1a609db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:35:48 +0000 Subject: [PATCH 002/162] Update github/codeql-action action to v3.29.1 --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 817ab007f246..18f07c8a298e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/init@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -53,6 +53,6 @@ jobs: -Dscan.tag.CodeQL \ classes - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/analyze@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 18cacbc0cc06..67105a0d426c 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: sarif_file: results.sarif From d054fe68d558bc027ee0c2b38e74b5cb20063c72 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 27 Jun 2025 17:46:09 +0200 Subject: [PATCH 003/162] Create initial 6.0.0-M2 release notes from template --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes/release-notes-6.0.0-M2.adoc | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index d3e080b7985f..499fe4b4ffb8 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -17,6 +17,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-6.0.0-M2.adoc[] + include::{basedir}/release-notes-6.0.0-M1.adoc[] include::{basedir}/release-notes-5.13.3.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc new file mode 100644 index 000000000000..f3534088425e --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -0,0 +1,67 @@ +[[release-notes-6.0.0-M2]] +== 6.0.0-M2 + +*Date of Release:* ❓ + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit-framework-repo}+/milestone/99?closed=1+[6.0.0-M2] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-6.0.0-M2-junit-platform]] +=== JUnit Platform + +[[release-notes-6.0.0-M2-junit-platform-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-6.0.0-M2-junit-platform-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-6.0.0-M2-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-6.0.0-M2-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-6.0.0-M2-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-6.0.0-M2-junit-vintage]] +=== JUnit Vintage + +[[release-notes-6.0.0-M2-junit-vintage-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-6.0.0-M2-junit-vintage-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-6.0.0-M2-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ From 4c9096c075f448ecb45599fe8b8229407a9c2612 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 27 Jun 2025 17:54:25 +0200 Subject: [PATCH 004/162] Remove leftover "jfr" node from component diagram --- documentation/src/plantuml/component-diagram.puml | 2 -- 1 file changed, 2 deletions(-) diff --git a/documentation/src/plantuml/component-diagram.puml b/documentation/src/plantuml/component-diagram.puml index a0668125eb61..df2b1391089d 100644 --- a/documentation/src/plantuml/component-diagram.puml +++ b/documentation/src/plantuml/component-diagram.puml @@ -73,8 +73,6 @@ console ..> reporting launcher ..> engine -jfr ..> launcher - engine ....> opentest4j engine ..> commons From 10afb1646ac4fbaeb18f52177b3db38480913a9d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 27 Jun 2025 18:12:53 +0200 Subject: [PATCH 005/162] Discontinue junit-platform-suite-commons The sole reason for this module was sharing code between junit-platform-suite-engine and junit-platform-runner. Since the latter has been discontinued, this commit merges the two classes from junit-platform-suite-commons into junit-platform-suite-engine. --- build.gradle.kts | 1 - .../release-notes/release-notes-6.0.0-M2.adoc | 3 +- .../junit-platform-suite-engine.adoc | 1 - .../docs/asciidoc/user-guide/appendix.adoc | 2 - .../src/plantuml/component-diagram.puml | 7 +- .../src/main/java/module-info.java | 1 - .../core/ClasspathAlignmentChecker.java | 1 - .../junit-platform-suite-commons.gradle.kts | 24 ---- .../src/main/java/module-info.java | 28 ---- .../platform/suite/commons/package-info.java | 14 -- .../src/test/README.md | 1 - .../junit-platform-suite-engine.gradle.kts | 2 +- .../src/main/java/module-info.java | 1 - .../engine}/AdditionalDiscoverySelectors.java | 2 +- .../SuiteLauncherDiscoveryRequestBuilder.java | 121 ++++-------------- .../suite/engine/SuiteTestDescriptor.java | 7 +- platform-tests/platform-tests.gradle.kts | 1 - ...eLauncherDiscoveryRequestBuilderTests.java | 26 ++-- .../junit-platform-commons.expected.txt | 2 +- .../junit-platform-suite-commons.expected.txt | 9 -- .../junit-platform-suite-engine.expected.txt | 1 - .../platform/tooling/support/HelperTests.java | 1 - settings.gradle.kts | 1 - 23 files changed, 47 insertions(+), 210 deletions(-) delete mode 100644 junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts delete mode 100644 junit-platform-suite-commons/src/main/java/module-info.java delete mode 100644 junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java delete mode 100644 junit-platform-suite-commons/src/test/README.md rename {junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons => junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine}/AdditionalDiscoverySelectors.java (99%) rename {junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons => junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine}/SuiteLauncherDiscoveryRequestBuilder.java (80%) rename platform-tests/src/test/java/org/junit/platform/suite/{commons => engine}/SuiteLauncherDiscoveryRequestBuilderTests.java (94%) delete mode 100644 platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt diff --git a/build.gradle.kts b/build.gradle.kts index 21ecf2b5bc2f..ac2b1c43ec54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,7 +27,6 @@ val platformProjects by extra(listOf( projects.junitPlatformReporting, projects.junitPlatformSuite, projects.junitPlatformSuiteApi, - projects.junitPlatformSuiteCommons, projects.junitPlatformSuiteEngine, projects.junitPlatformTestkit ).map { dependencyProject(it) }) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index f3534088425e..fdcdfefe2345 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -21,7 +21,8 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes -* ❓ +* Discontinue `junit-platform-suite-commons` which is now integrated into + `junit-platform-suite` [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc index ebb1d6495b04..366d4854abb6 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc @@ -26,7 +26,6 @@ on `junit-platform-suite-api` and `junit-platform-suite-engine`. [[junit-platform-suite-engine-setup-transitive-dependencies]] ===== Transitive Dependencies -* `junit-platform-suite-commons` in _test_ scope * `junit-platform-launcher` in _test_ scope * `junit-platform-engine` in _test_ scope * `junit-platform-commons` in _test_ scope diff --git a/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/documentation/src/docs/asciidoc/user-guide/appendix.adoc index 567f17cdf02b..4ddfee636df7 100644 --- a/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ b/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -78,8 +78,6 @@ Please refer to the corresponding sections for <>. - `junit-platform-suite-commons`:: - Common support utilities for executing test suites on the JUnit Platform. `junit-platform-suite-engine`:: Engine that executes test suites on the JUnit Platform; only required at runtime. See <> for details. diff --git a/documentation/src/plantuml/component-diagram.puml b/documentation/src/plantuml/component-diagram.puml index df2b1391089d..a910c17dcef3 100644 --- a/documentation/src/plantuml/component-diagram.puml +++ b/documentation/src/plantuml/component-diagram.puml @@ -24,7 +24,6 @@ package org.junit.platform { [junit-platform-reporting] as reporting [junit-platform-suite] as suite [junit-platform-suite-api] as suite_api - [junit-platform-suite-commons] as suite_commons [junit-platform-suite-engine] as suite_engine [junit-platform-testkit] as testkit } @@ -82,10 +81,8 @@ reporting ......> otr_tooling_spi suite ..> suite_api suite ..> suite_engine -suite_engine ..> suite_commons - -suite_commons ..> launcher -suite_commons ..> suite_api +suite_engine ..> launcher +suite_engine ..> suite_api testkit ....> opentest4j testkit ..> launcher diff --git a/junit-platform-commons/src/main/java/module-info.java b/junit-platform-commons/src/main/java/module-info.java index c500d7624f09..3b83d2ffc150 100644 --- a/junit-platform-commons/src/main/java/module-info.java +++ b/junit-platform-commons/src/main/java/module-info.java @@ -53,7 +53,6 @@ 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/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 312fe2519ad7..5511b56db1fe 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 @@ -41,7 +41,6 @@ class ClasspathAlignmentChecker { "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/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts b/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts deleted file mode 100644 index cb43e5d6963c..000000000000 --- a/junit-platform-suite-commons/junit-platform-suite-commons.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - id("junitbuild.java-library-conventions") - id("junitbuild.java-nullability-conventions") -} - -description = "JUnit Platform Suite Commons" - -dependencies { - api(platform(projects.junitBom)) - api(projects.junitPlatformLauncher) - - compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) - - implementation(projects.junitPlatformEngine) - implementation(projects.junitPlatformSuiteApi) - - osgiVerification(projects.junitJupiterEngine) - osgiVerification(projects.junitPlatformLauncher) -} - -tasks.compileJava { - options.compilerArgs.add("-Xlint:-module") // due to qualified exports -} diff --git a/junit-platform-suite-commons/src/main/java/module-info.java b/junit-platform-suite-commons/src/main/java/module-info.java deleted file mode 100644 index f0514a14064d..000000000000 --- a/junit-platform-suite-commons/src/main/java/module-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 - */ - -/** - * Common support utilities for declarative test suites. - * - * @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; - requires transitive org.junit.platform.launcher; - - exports org.junit.platform.suite.commons to - org.junit.platform.suite.engine; -} 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 deleted file mode 100644 index 422ebf466472..000000000000 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/package-info.java +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Common support utilities for executing test suites on the JUnit Platform. - * - *

DISCLAIMER

- * - *

Any API annotated with {@code @API(status = INTERNAL)} is intended solely - * for usage within the JUnit framework itself. Any usage of internal - * 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-commons/src/test/README.md b/junit-platform-suite-commons/src/test/README.md deleted file mode 100644 index 6e2fd0b363f0..000000000000 --- a/junit-platform-suite-commons/src/test/README.md +++ /dev/null @@ -1 +0,0 @@ -For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. 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 7e777400ca8b..e44887b6cdcb 100644 --- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -13,7 +13,7 @@ dependencies { compileOnlyApi(libs.apiguardian) compileOnly(libs.jspecify) - implementation(projects.junitPlatformSuiteCommons) + implementation(projects.junitPlatformLauncher) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) 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 e7e15543dba2..0f321578dc39 100644 --- a/junit-platform-suite-engine/src/main/java/module-info.java +++ b/junit-platform-suite-engine/src/main/java/module-info.java @@ -21,7 +21,6 @@ requires static org.jspecify; requires org.junit.platform.suite.api; - requires org.junit.platform.suite.commons; requires org.junit.platform.commons; requires org.junit.platform.engine; requires org.junit.platform.launcher; diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java similarity index 99% rename from junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java rename to junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java index bbfe6aa026ac..8def4993f7c8 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.suite.commons; +package org.junit.platform.suite.engine; import java.util.Arrays; import java.util.List; diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java similarity index 80% rename from junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java rename to junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java index d0a4464f23ed..3688d094fe12 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java @@ -8,29 +8,25 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.suite.commons; +package org.junit.platform.suite.engine; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; -import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectClasspathResource; -import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectFile; +import static org.junit.platform.suite.engine.AdditionalDiscoverySelectors.selectClasspathResource; +import static org.junit.platform.suite.engine.AdditionalDiscoverySelectors.selectFile; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.regex.Pattern; 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; @@ -74,47 +70,13 @@ * for generating a {@link LauncherDiscoveryRequest} specifically tailored for * suite execution. * - *

Example

- * - *
{@code
- * SuiteLauncherDiscoveryRequestBuilder.request()
- *   .selectors(
- *        selectPackage("org.example.user"),
- *        selectClass("org.example.payment.PaymentTests"),
- *        selectClass(ShippingTests.class),
- *        selectMethod("org.example.order.OrderTests#test1"),
- *        selectMethod("org.example.order.OrderTests#test2()"),
- *        selectMethod("org.example.order.OrderTests#test3(java.lang.String)"),
- *        selectMethod("org.example.order.OrderTests", "test4"),
- *        selectMethod(OrderTests.class, "test5"),
- *        selectMethod(OrderTests.class, testMethod),
- *        selectClasspathRoots(Collections.singleton(Paths.get("/my/local/path1"))),
- *        selectUniqueId("unique-id-1"),
- *        selectUniqueId("unique-id-2")
- *   )
- *   .filters(
- *        includeEngines("junit-jupiter", "spek"),
- *        // excludeEngines("junit-vintage"),
- *        includeTags("fast"),
- *        // excludeTags("slow"),
- *        includeClassNamePatterns(".*Test[s]?")
- *        // includeClassNamePatterns("org\.example\.tests.*")
- *   )
- *   .configurationParameter("key", "value")
- *   .enableImplicitConfigurationParameters(true)
- *   .applyConfigurationParametersFromSuite(MySuite.class)
- *   .applySelectorsAndFiltersFromSuite(MySuite.class)
- *   .build();
- * }
- * - * @since 1.8 + * @since 1.8 (originally in junit-platform-suite-commons) * @see org.junit.platform.engine.discovery.DiscoverySelectors * @see org.junit.platform.engine.discovery.ClassNameFilter * @see org.junit.platform.launcher.EngineFilter * @see org.junit.platform.launcher.TagFilter */ -@API(status = INTERNAL, since = "1.8", consumers = "org.junit.platform.suite.engine") -public final class SuiteLauncherDiscoveryRequestBuilder { +final class SuiteLauncherDiscoveryRequestBuilder { private final LauncherDiscoveryRequestBuilder delegate = LauncherDiscoveryRequestBuilder.request(); private final Set selectedClassNames = new LinkedHashSet<>(); @@ -133,7 +95,7 @@ private SuiteLauncherDiscoveryRequestBuilder() { * * @return a new builder */ - public static SuiteLauncherDiscoveryRequestBuilder request() { + static SuiteLauncherDiscoveryRequestBuilder request() { return new SuiteLauncherDiscoveryRequestBuilder(); } @@ -141,30 +103,24 @@ public static SuiteLauncherDiscoveryRequestBuilder request() { * Add all supplied {@code selectors} to the request. * * @param selectors the {@code DiscoverySelectors} to add; never {@code null} - * @return this builder for method chaining */ - public SuiteLauncherDiscoveryRequestBuilder selectors(DiscoverySelector... selectors) { + void selectors(DiscoverySelector... selectors) { this.delegate.selectors(selectors); - return this; } /** * Add all supplied {@code selectors} to the request. * * @param selectors the {@code DiscoverySelectors} to add; never {@code null} - * @return this builder for method chaining */ - public SuiteLauncherDiscoveryRequestBuilder selectors(List selectors) { + private void selectors(List selectors) { this.delegate.selectors(selectors); - return this; } /** * Add all supplied {@code filters} to the request. - * *

The {@code filters} are combined using AND semantics, i.e. all of them * have to include a resource for it to end up in the test plan. - * *

Warning: be cautious when registering multiple competing * {@link EngineFilter#includeEngines include} {@code EngineFilters} or multiple * competing {@link EngineFilter#excludeEngines exclude} {@code EngineFilters} @@ -172,24 +128,19 @@ public SuiteLauncherDiscoveryRequestBuilder selectors(List... filters) { + private void filters(Filter... filters) { this.delegate.filters(filters); - return this; } /** * Specify whether to filter standard class name patterns. *

If set to {@code true}, standard class name patterns are filtered. * - * @param filterStandardClassNamePatterns {@code true} to filter standard class - * name patterns, {@code false} otherwise * @return this builder for method chaining */ - public SuiteLauncherDiscoveryRequestBuilder filterStandardClassNamePatterns( - boolean filterStandardClassNamePatterns) { - this.filterStandardClassNamePatterns = filterStandardClassNamePatterns; + SuiteLauncherDiscoveryRequestBuilder filterStandardClassNamePatterns() { + this.filterStandardClassNamePatterns = true; return this; } @@ -201,45 +152,29 @@ public SuiteLauncherDiscoveryRequestBuilder filterStandardClassNamePatterns( * @param value the value to store * @return this builder for method chaining */ - public SuiteLauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { + SuiteLauncherDiscoveryRequestBuilder configurationParameter(String key, String value) { this.delegate.configurationParameter(key, value); return this; } - /** - * Add all supplied configuration parameters to the request. - * - * @param configurationParameters the map of configuration parameters to add; - * never {@code null} - * @return this builder for method chaining - * @see #configurationParameter(String, String) - */ - public SuiteLauncherDiscoveryRequestBuilder configurationParameters(Map configurationParameters) { - this.delegate.configurationParameters(configurationParameters); - return this; - } - - public SuiteLauncherDiscoveryRequestBuilder configurationParametersResource(String resourceFile) { + void configurationParametersResource(String resourceFile) { this.delegate.configurationParametersResources(resourceFile); - return this; } /** * Set the parent configuration parameters to use for the request. * *

Any explicit configuration parameters configured via - * {@link #configurationParameter(String, String)} or - * {@link #configurationParameters(Map)} takes precedence over the supplied - * configuration parameters. + * {@link #configurationParameter(String, String)} takes precedence over the + * supplied configuration parameters. * * @param parentConfigurationParameters the parent instance to use for looking * up configuration parameters that have not been explicitly configured; * never {@code null} * @return this builder for method chaining * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) */ - public SuiteLauncherDiscoveryRequestBuilder parentConfigurationParameters( + SuiteLauncherDiscoveryRequestBuilder parentConfigurationParameters( ConfigurationParameters parentConfigurationParameters) { this.parentConfigurationParameters = parentConfigurationParameters; return this; @@ -247,34 +182,27 @@ public SuiteLauncherDiscoveryRequestBuilder parentConfigurationParameters( /** * Configure whether implicit configuration parameters should be considered. - * *

By default, in addition to those parameters that are passed explicitly * to this builder, configuration parameters are read from system properties * and from the {@code junit-platform.properties} classpath resource. * Passing {@code false} to this method, disables the latter two sources so * that only explicit configuration parameters are taken into account. * - * @param enabled {@code true} if implicit configuration parameters should be - * considered * @return this builder for method chaining * @see #configurationParameter(String, String) - * @see #configurationParameters(Map) */ - public SuiteLauncherDiscoveryRequestBuilder enableImplicitConfigurationParameters(boolean enabled) { - this.delegate.enableImplicitConfigurationParameters(enabled); + SuiteLauncherDiscoveryRequestBuilder disableImplicitConfigurationParameters() { + this.delegate.enableImplicitConfigurationParameters(false); return this; } - public SuiteLauncherDiscoveryRequestBuilder outputDirectoryProvider( - OutputDirectoryProvider outputDirectoryProvider) { + SuiteLauncherDiscoveryRequestBuilder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryProvider) { delegate.outputDirectoryProvider(outputDirectoryProvider); return this; } - @API(status = INTERNAL, since = "1.13") - public SuiteLauncherDiscoveryRequestBuilder listener(LauncherDiscoveryListener listener) { + void listener(LauncherDiscoveryListener listener) { delegate.listeners(listener); - return this; } /** @@ -289,8 +217,7 @@ public SuiteLauncherDiscoveryRequestBuilder listener(LauncherDiscoveryListener l * {@link #applySelectorsAndFiltersFromSuite} */ @Deprecated - @API(status = DEPRECATED, since = "1.11") - public SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { + SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); applyConfigurationParametersFromSuite(suiteClass); applySelectorsAndFiltersFromSuite(suiteClass); @@ -312,7 +239,7 @@ public SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { * @since 1.11 * @see org.junit.platform.suite.api.Suite */ - public SuiteLauncherDiscoveryRequestBuilder applyConfigurationParametersFromSuite(Class suiteClass) { + SuiteLauncherDiscoveryRequestBuilder applyConfigurationParametersFromSuite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); // @formatter:off @@ -357,7 +284,7 @@ public SuiteLauncherDiscoveryRequestBuilder applyConfigurationParametersFromSuit * @since 1.11 * @see org.junit.platform.suite.api.Suite */ - public SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Class suiteClass) { + SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); addExcludeFilters(suiteClass); @@ -444,7 +371,7 @@ private void addOtherSelectors(Class suiteClass) { * Build the {@link LauncherDiscoveryRequest} that has been configured via * this builder. */ - public LauncherDiscoveryRequest build() { + LauncherDiscoveryRequest build() { if (this.filterStandardClassNamePatterns && !this.includeClassNamePatternsUsed) { this.delegate.filters(createIncludeClassNameFilter(STANDARD_INCLUDE_PATTERN)); } 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 161c5189aa91..356fe4e5ff17 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 @@ -15,7 +15,7 @@ import static java.util.stream.Collectors.joining; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilder.request; import java.lang.reflect.Method; import java.util.List; @@ -50,7 +50,6 @@ import org.junit.platform.launcher.listeners.TestExecutionSummary; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.api.SuiteDisplayName; -import org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder; /** * {@link TestDescriptor} for tests based on the JUnit Platform Suite API. @@ -118,8 +117,8 @@ void discover() { // @formatter:off LauncherDiscoveryRequest request = discoveryRequestBuilder - .filterStandardClassNamePatterns(true) - .enableImplicitConfigurationParameters(false) + .filterStandardClassNamePatterns() + .disableImplicitConfigurationParameters() .parentConfigurationParameters(configurationParameters) .applyConfigurationParametersFromSuite(suiteClass) .outputDirectoryProvider(outputDirectoryProvider) diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 604850053ece..ae184107e8fc 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -37,7 +37,6 @@ dependencies { testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformEngine) testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteCommons) testImplementation(projects.junitPlatformSuiteEngine) // --- Things we are testing with --------------------------------------------- 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/engine/SuiteLauncherDiscoveryRequestBuilderTests.java similarity index 94% rename from platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java rename to platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java index efa86382c9bc..47f857bc6704 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.suite.commons; +package org.junit.platform.suite.engine; import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; @@ -17,7 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilder.request; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -215,7 +215,7 @@ class Suite { // @formatter:off LauncherDiscoveryRequest request = builder - .filterStandardClassNamePatterns(true) + .filterStandardClassNamePatterns() .applySelectorsAndFiltersFromSuite(Suite.class) .build(); // @formatter:on @@ -274,7 +274,7 @@ class Suite { @Test void selectClassesByName() { - @SelectClasses(names = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase") + @SelectClasses(names = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase") class Suite { } @@ -302,13 +302,13 @@ static class NonLocalTestCase { @TestFactory Stream selectOneMethodWithNoParameters() { - @SelectMethod("org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod") + @SelectMethod("org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod") class SuiteA { } @SelectMethod(type = NoParameterTestCase.class, name = "testMethod") class SuiteB { } - @SelectMethod(typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase", name = "testMethod") + @SelectMethod(typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase", name = "testMethod") class SuiteC { } @@ -329,7 +329,7 @@ void testMethod() { @TestFactory Stream selectOneMethodWithOneParameter() { - @SelectMethod("org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase#testMethod(int)") + @SelectMethod("org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase#testMethod(int)") class SuiteA { } @SelectMethod(type = OneParameterTestCase.class, name = "testMethod", parameterTypeNames = "int") @@ -338,10 +338,10 @@ class SuiteB { @SelectMethod(type = OneParameterTestCase.class, name = "testMethod", parameterTypes = int.class) class SuiteC { } - @SelectMethod(typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypeNames = "int") + @SelectMethod(typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypeNames = "int") class SuiteD { } - @SelectMethod(typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypes = int.class) + @SelectMethod(typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$OneParameterTestCase", name = "testMethod", parameterTypes = int.class) class SuiteE { } @@ -390,7 +390,7 @@ Stream selectMethodCausesExceptionOnInvalidUsage() { @SelectMethod(value = "irrelevant", type = NoParameterTestCase.class) class ValueAndType { } - @SelectMethod(value = "SomeClass#someMethod", typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase") + @SelectMethod(value = "SomeClass#someMethod", typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase") class ValueAndTypeName { } @SelectMethod(value = "SomeClass#someMethod", name = "testMethod") @@ -405,7 +405,7 @@ class ValueAndParameterTypeNames { @SelectMethod(type = NoParameterTestCase.class) class MissingMethodName { } - @SelectMethod(name = "testMethod", type = NoParameterTestCase.class, typeName = "org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase") + @SelectMethod(name = "testMethod", type = NoParameterTestCase.class, typeName = "org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase") class TypeAndTypeName { } @SelectMethod(name = "testMethod", parameterTypes = int.class, parameterTypeNames = "int") @@ -633,8 +633,8 @@ class Suite { void selectByIdentifier() { // @formatter:off @Select({ - "class:org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase", - "method:org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod" + "class:org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NonLocalTestCase", + "method:org.junit.platform.suite.engine.SuiteLauncherDiscoveryRequestBuilderTests$NoParameterTestCase#testMethod" }) // @formatter:on class Suite { 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 4a2879865c72..dcc0038d88fb 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 @@ -15,4 +15,4 @@ 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.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 +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.launcher org.junit.platform.reporting org.junit.platform.suite.api 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-suite-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt deleted file mode 100644 index 1d5c6b9f584c..000000000000 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-suite-commons.expected.txt +++ /dev/null @@ -1,9 +0,0 @@ -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 -requires org.junit.platform.suite.api -qualified exports org.junit.platform.suite.commons to org.junit.platform.suite.engine 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 d655ec351ed3..fb75824c826c 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 @@ -6,6 +6,5 @@ requires org.junit.platform.commons requires org.junit.platform.engine requires org.junit.platform.launcher requires org.junit.platform.suite.api -requires org.junit.platform.suite.commons provides org.junit.platform.engine.TestEngine with org.junit.platform.suite.engine.SuiteTestEngine contains org.junit.platform.suite.engine diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java index 156d19e0bbb7..d755f73cd5e8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java @@ -43,7 +43,6 @@ void loadModuleDirectoryNames() { "junit-platform-reporting", // "junit-platform-suite", // "junit-platform-suite-api", // - "junit-platform-suite-commons", // "junit-platform-suite-engine", // "junit-platform-testkit", // "junit-vintage-engine"// diff --git a/settings.gradle.kts b/settings.gradle.kts index 9c65c66f87c3..b797a48cb6db 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -85,7 +85,6 @@ include("junit-platform-launcher") include("junit-platform-reporting") include("junit-platform-suite") include("junit-platform-suite-api") -include("junit-platform-suite-commons") include("junit-platform-suite-engine") include("junit-platform-testkit") include("junit-vintage-engine") From 5a729426d06ca2259f31b933ecf0bbdffb02c939 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 27 Jun 2025 19:16:42 +0200 Subject: [PATCH 006/162] Polish release notes --- .../docs/asciidoc/release-notes/release-notes-5.13.2.adoc | 6 +++--- .../docs/asciidoc/release-notes/release-notes-5.13.3.adoc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc index ae95787331ba..a025895f3e20 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc @@ -3,7 +3,7 @@ *Date of Release:* June 24, 2025 -*Scope:* Bug fixes and enhancements since 5.13.0 +*Scope:* Bug fixes and enhancements since 5.13.1 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/98?closed=1+[5.13.2] milestone page in the JUnit @@ -33,8 +33,8 @@ repository on GitHub. `@Nested`. * Stop reporting discovery issues for _abstract_ inner classes that contain test methods but are not annotated with `@Nested`. -* Stop reporting discovery issues for _abstract_ test methods. While they won't be - executed, it's a valid pattern to annotate them with `@Test` for documentation purposes +* Stop reporting discovery issues for _abstract_ test methods. Although they will not be + executed, it is a valid pattern to annotate them with `@Test` for documentation purposes and override them in subclasses while re-declaring the `@Test` annotation. [[release-notes-5.13.2-junit-jupiter-new-features-and-improvements]] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index 69472a7905df..dc6af824bf77 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -3,7 +3,7 @@ *Date of Release:* ❓ -*Scope:* ❓ +*Scope:* Bug fixes and enhancements since 5.13.2 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/100?closed=1+[5.13.3] milestone page in the JUnit From 5b515856c963acbf19daa205feba3240edead6d0 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sat, 28 Jun 2025 10:29:21 +0200 Subject: [PATCH 007/162] Polish release notes --- .../release-notes/release-notes-6.0.0-M1.adoc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 d43f17f1d5fd..0c8e2d067e65 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 @@ -5,14 +5,14 @@ *Scope:* -* Require Java 17 and Kotlin 2.2 +* Java 17 and Kotlin 2.2 baseline * Single version number for Platform, Jupiter, and Vintage -* Use JSpecify's annotations to document nullability -* Remove various deprecated behaviors and APIs -* Integrate JFR functionality into junit-platform-launcher -* Discontinue junit-platform-runner and junit-platform-jfr -* Switch to FastCSV library for `@Csv\{File}Source` -* Add support for using Kotlin `suspend` functions as test methods +* Use of JSpecify annotations to document nullability +* Removal of various deprecated behaviors and APIs +* Integration of JFR functionality into `junit-platform-launcher` +* Removal of `junit-platform-runner` and `junit-platform-jfr` +* Switch to FastCSV library for `@CsvSource` and `@CsvFileSource` +* Support for using Kotlin `suspend` functions as test methods For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/87?closed=1+[6.0.0-M1] milestone page in the JUnit @@ -27,12 +27,12 @@ repository on GitHub. * Minimum required Java version is now 17. * Minimum required Kotlin version is now 2.2. -* Platform, Jupiter, and Vintage artifacts now use the same version number. +* Platform artifacts now use the same version number as Jupiter and Vintage artifacts. [[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 +* All JUnit modules now use https://jspecify.dev/[JSpecify] nullability annotations to indicate which method parameters, return types, etc. can be `null`. From 9a6b0a7110396ad04fe342723f5ed9c8a235df8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:43:22 +0000 Subject: [PATCH 008/162] Update dependency com.puppycrawl.tools:checkstyle to v10.26.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9390d0778395..431cc7c66a50 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ asciidoctorj-pdf = "2.3.19" asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.27.3" bnd = "7.1.0" -checkstyle = "10.26.0" +checkstyle = "10.26.1" eclipse = "4.36.0" jackson = "2.19.1" jacoco = "0.8.13" From 3f28207cad546274f1c036a546f1a26d2a1c142b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:14:40 +0200 Subject: [PATCH 009/162] Switch to String#isBlank and String#strip Since we now have a Java 17 baseline, this commit makes use of String#isBlank and String#strip (instead of String#trim) throughout the code base. Closes #4697 --- .../docs/asciidoc/user-guide/running-tests.adoc | 10 +++++----- .../test/java/example/ParameterizedTestDemo.java | 4 ++-- .../org/junit/jupiter/api/AssertLinesMatch.java | 6 +++--- .../junit/jupiter/api/DisplayNameGenerator.java | 4 ++-- .../src/main/java/org/junit/jupiter/api/Tag.java | 2 +- .../DisabledIfEnvironmentVariableCondition.java | 2 +- .../DisabledIfSystemPropertyCondition.java | 2 +- .../EnabledIfEnvironmentVariableCondition.java | 2 +- .../EnabledIfSystemPropertyCondition.java | 2 +- .../EnumConfigurationParameterConverter.java | 2 +- ...stantiatingConfigurationParameterConverter.java | 2 +- .../engine/descriptor/DisplayNameUtils.java | 2 +- .../engine/extension/AutoCloseExtension.java | 2 +- .../engine/extension/RepeatedTestExtension.java | 2 +- .../ParameterizedInvocationNameFormatter.java | 2 +- .../params/converter/DefaultArgumentConverter.java | 2 +- .../params/provider/FieldArgumentsProvider.java | 2 +- .../support/scanning/DefaultClasspathScanner.java | 4 ++-- .../commons/util/ClassNamePatternFilterUtils.java | 4 ++-- .../platform/commons/util/ReflectionUtils.java | 12 ++++++------ .../junit/platform/commons/util/StringUtils.java | 3 ++- .../console/tasks/DetailsPrintingListener.java | 2 +- .../java/org/junit/platform/engine/TestTag.java | 14 +++++++------- .../engine/discovery/DiscoverySelectors.java | 14 +++++++------- .../org/junit/platform/launcher/EngineFilter.java | 2 +- .../org/junit/platform/launcher/TagFilter.java | 2 +- .../core/LauncherConfigurationParameters.java | 2 +- .../launcher/tagexpression/ParseStatus.java | 4 ++-- .../platform/launcher/tagexpression/Token.java | 4 ++-- .../reporting/legacy/xml/XmlReportWriter.java | 2 +- .../org/junit/platform/suite/api/ExcludeTags.java | 8 ++++---- .../org/junit/platform/suite/api/IncludeTags.java | 8 ++++---- .../SuiteLauncherDiscoveryRequestBuilder.java | 12 ++++++------ .../platform/testkit/engine/EngineTestKit.java | 6 +++--- .../params/ParameterizedClassIntegrationTests.java | 2 +- .../launcher/core/StreamInterceptorTests.java | 4 ++-- .../launcher/tagexpression/TokenTests.java | 6 +++--- .../reporting/legacy/xml/XmlReportWriterTests.java | 4 ++-- .../xml/OpenTestReportGeneratingListenerTests.java | 2 +- .../support/tests/JarDescribeModuleTests.java | 4 ++-- 40 files changed, 88 insertions(+), 87 deletions(-) diff --git a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc index b6533581fba2..10a9981c71d8 100644 --- a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc @@ -961,9 +961,9 @@ own annotation or other means for users to specify tags. Regardless how a tag is specified, the JUnit Platform enforces the following rules: * A tag must not be `null` or _blank_. -* A _trimmed_ tag must not contain whitespace. -* A _trimmed_ tag must not contain ISO control characters. -* A _trimmed_ tag must not contain any of the following _reserved characters_. +* A _stripped_ tag must not contain whitespace. +* A _stripped_ tag must not contain ISO control characters. +* A _stripped_ tag must not contain any of the following _reserved characters_. - `,`: _comma_ - `(`: _left parenthesis_ - `)`: _right parenthesis_ @@ -971,8 +971,8 @@ Regardless how a tag is specified, the JUnit Platform enforces the following rul - `|`: _vertical bar_ - `!`: _exclamation point_ -NOTE: In the above context, "trimmed" means that leading and trailing whitespace -characters have been removed. +NOTE: In the above context, "stripped" means that leading and trailing whitespace +characters have been removed using `java.lang.String.strip()`. [[running-tests-tag-expressions]] ==== Tag Expressions diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index a7312f584cc5..72caa35d7ba4 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -108,7 +108,7 @@ class NullAndEmptySource_1 { @EmptySource @ValueSource(strings = { " ", " ", "\t", "\n" }) void nullEmptyAndBlankStrings(String text) { - assertTrue(text == null || text.trim().isEmpty()); + assertTrue(text == null || text.isBlank()); } // end::NullAndEmptySource_example1[] } @@ -121,7 +121,7 @@ class NullAndEmptySource_2 { @NullAndEmptySource @ValueSource(strings = { " ", " ", "\t", "\n" }) void nullEmptyAndBlankStrings(String text) { - assertTrue(text == null || text.trim().isEmpty()); + assertTrue(text == null || text.isBlank()); } // end::NullAndEmptySource_example2[] } 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 16921e088eff..990d12b94f29 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 @@ -198,13 +198,13 @@ void fail(String format, Object... args) { } static boolean isFastForwardLine(String line) { - line = line.trim(); + line = line.strip(); return line.length() >= 4 && line.startsWith(">>") && line.endsWith(">>"); } static int parseFastForwardLimit(String fastForwardLine) { - fastForwardLine = fastForwardLine.trim(); - String text = fastForwardLine.substring(2, fastForwardLine.length() - 2).trim(); + fastForwardLine = fastForwardLine.strip(); + String text = fastForwardLine.substring(2, fastForwardLine.length() - 2).strip(); try { int limit = Integer.parseInt(text); condition(limit > 0, () -> "fast-forward(%d) limit must be greater than zero".formatted(limit)); 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 7e4fc350c915..c7dbd2e5f155 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 @@ -391,7 +391,7 @@ private String getSentenceBeginning(Class testClass, List> enclosing String sentenceFragment = findAnnotation(testClass, DisplayName.class)// .map(DisplayName::value)// - .map(String::trim)// + .map(String::strip)// .orElseGet(() -> getSentenceFragment(testClass)); if (enclosingClass == null || isStatic(testClass)) { // top-level class @@ -508,7 +508,7 @@ private static Optional findIndicativeSentencesGe .map(sentenceFragment -> { Preconditions.notBlank(sentenceFragment, "@SentenceFragment on [%s] must be declared with a non-blank value.".formatted(element)); - return sentenceFragment.trim(); + return sentenceFragment.strip(); }) // .orElse(null); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java index ffc31b21ffcc..2156882183ae 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java @@ -65,7 +65,7 @@ /** * The tag. * - *

Note: the tag will first be {@linkplain String#trim() trimmed}. If the + *

Note: the tag will first be {@linkplain String#strip() stripped}. If the * supplied tag is syntactically invalid after trimming, the error will be * logged as a warning, and the invalid tag will be effectively ignored. See * {@linkplain Tag Syntax Rules for Tags}. 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 968eca1a4e28..c3005bfff31c 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 @@ -41,7 +41,7 @@ protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { @Override protected ConditionEvaluationResult evaluate(DisabledIfEnvironmentVariable annotation) { - String name = annotation.named().trim(); + String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java index 3c260a47c778..0350123d7dd2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java @@ -39,7 +39,7 @@ protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { @Override protected ConditionEvaluationResult evaluate(DisabledIfSystemProperty annotation) { - String name = annotation.named().trim(); + String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); 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 1885aa9c8a39..8ee1f059c86c 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 @@ -42,7 +42,7 @@ protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { @Override protected ConditionEvaluationResult evaluate(EnabledIfEnvironmentVariable annotation) { - String name = annotation.named().trim(); + String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java index c3baab3768f5..788473f2723f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java @@ -40,7 +40,7 @@ protected ConditionEvaluationResult getNoDisabledConditionsEncounteredResult() { @Override protected ConditionEvaluationResult evaluate(EnabledIfSystemProperty annotation) { - String name = annotation.named().trim(); + String name = annotation.named().strip(); String regex = annotation.matches(); Preconditions.notBlank(name, () -> "The 'named' attribute must not be blank in " + annotation); Preconditions.notBlank(regex, () -> "The 'matches' attribute must not be blank in " + annotation); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java index d0a470aed8ec..6225a7ddbf3b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java @@ -51,7 +51,7 @@ public E get(String key, Function> lookup, E defaultVal if (value.isPresent()) { String constantName = null; try { - constantName = value.get().trim().toUpperCase(Locale.ROOT); + constantName = value.get().strip().toUpperCase(Locale.ROOT); E result = Enum.valueOf(enumType, constantName); logger.config(() -> "Using %s '%s' set via the '%s' configuration parameter.".formatted(enumDisplayName, result, key)); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java index 5f2eaf34745b..b11896fa039e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java @@ -41,7 +41,7 @@ Optional get(ConfigurationParameters configurationParameters, String key) { Supplier> supply(ConfigurationParameters configurationParameters, String key) { // @formatter:off return configurationParameters.get(key) - .map(String::trim) + .map(String::strip) .filter(className -> !className.isEmpty()) .map(className -> newInstanceSupplier(className, key)) .orElse(Optional::empty); 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 bb8d5e5427b2..beaf49db101c 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 @@ -76,7 +76,7 @@ static String determineDisplayName(AnnotatedElement element, Supplier di return findAnnotation(element, DisplayName.class) // .map(DisplayName::value) // .filter(StringUtils::isNotBlank) // - .map(String::trim) // + .map(String::strip) // .orElseGet(displayNameSupplier); } 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 17652eef18ef..7f37221d1b6e 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 @@ -81,7 +81,7 @@ private static void closeField(Field field, @Nullable Object testInstance) throw logger.warn(() -> "Cannot @AutoClose field %s because it is null.".formatted(getQualifiedName(field))); } else { - invokeCloseMethod(field, fieldValue, methodName.trim()); + invokeCloseMethod(field, fieldValue, methodName.strip()); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java index 46b91c5e8a77..5217a5b73dac 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java @@ -77,7 +77,7 @@ private int failureThreshold(RepeatedTest repeatedTest, Method method) { private RepeatedTestDisplayNameFormatter displayNameFormatter(RepeatedTest repeatedTest, Method method, String displayName) { - String pattern = Preconditions.notBlank(repeatedTest.name().trim(), + String pattern = Preconditions.notBlank(repeatedTest.name().strip(), () -> "Configuration error: @RepeatedTest on method [%s] must be declared with a non-empty name.".formatted( method)); return new RepeatedTestDisplayNameFormatter(pattern, displayName); 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 82ac1a6f40a7..b1b61a4c5399 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 @@ -61,7 +61,7 @@ static ParameterizedInvocationNameFormatter create(ExtensionContext extensionCon ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY) // .orElse(DEFAULT_DISPLAY_NAME_PATTERN) : name; - pattern = Preconditions.notBlank(pattern.trim(), + pattern = Preconditions.notBlank(pattern.strip(), () -> "Configuration error: @%s on %s must be declared with a non-empty name.".formatted( declarationContext.getAnnotationName(), declarationContext.getResolverFacade().getIndexedParameterDeclarations().getSourceElementDescription())); 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 46c29d6a9cc0..d8ed795f80e2 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 @@ -72,7 +72,7 @@ public class DefaultArgumentConverter implements ArgumentConverter { public static final String DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME = "junit.jupiter.params.arguments.conversion.locale.format"; private static final Function TRANSFORMER = value -> LocaleConversionFormat.valueOf( - value.trim().toUpperCase(Locale.ROOT)); + value.strip().toUpperCase(Locale.ROOT)); private final ExtensionContext context; 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 fb4b47373981..eea09175a15a 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 @@ -73,7 +73,7 @@ protected Stream provideArguments(ParameterDeclarations par // package-private for testing static Field findField(Class testClass, String fieldName) { Preconditions.notBlank(fieldName, "Field name must not be blank"); - fieldName = fieldName.trim(); + fieldName = fieldName.strip(); Class clazz = testClass; if (fieldName.contains("#") || fieldName.contains(".")) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java index 427b8867d946..16d545daf141 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java @@ -87,7 +87,7 @@ public List> scanForClassesInPackage(String basePackageName, ClassFilte PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); Preconditions.notNull(classFilter, "classFilter must not be null"); - basePackageName = basePackageName.trim(); + basePackageName = basePackageName.strip(); List roots = getRootUrisForPackageNameOnClassPathAndModulePath(basePackageName); return findClassesForUris(roots, basePackageName, classFilter); @@ -107,7 +107,7 @@ public List scanForResourcesInPackage(String basePackageName, Predicat PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); - basePackageName = basePackageName.trim(); + basePackageName = basePackageName.strip(); List roots = getRootUrisForPackageNameOnClassPathAndModulePath(basePackageName); return findResourcesForUris(roots, basePackageName, resourceFilter); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java index bfe483dd0957..fd98cd61202c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java @@ -96,7 +96,7 @@ private static Predicate matchingClasses(@Nullable String patterns, Funct // @formatter:off return Optional.ofNullable(patterns) .filter(StringUtils::isNotBlank) - .map(String::trim) + .map(String::strip) .map(trimmedPatterns -> createPredicateFromPatterns(trimmedPatterns, classNameProvider, type)) .orElse(type == FilterType.EXCLUDE ? __ -> true : __ -> false); // @formatter:on @@ -120,7 +120,7 @@ private static List convertToRegularExpressions(String patterns) { // @formatter:off return Arrays.stream(patterns.split(",")) .filter(StringUtils::isNotBlank) - .map(String::trim) + .map(String::strip) .map(ClassNamePatternFilterUtils::replaceRegExElements) .map(Pattern::compile) .toList(); 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 d9bd8e4b4849..1364dead4875 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 @@ -747,15 +747,15 @@ public static Class loadRequiredClass(String name, ClassLoader classLoader) t public static Try> tryToLoadClass(String name, ClassLoader classLoader) { Preconditions.notBlank(name, "Class name must not be null or blank"); Preconditions.notNull(classLoader, "ClassLoader must not be null"); - String trimmedName = name.trim(); + String strippedName = name.strip(); - if (classNameToTypeMap.containsKey(trimmedName)) { - return Try.success(classNameToTypeMap.get(trimmedName)); + if (classNameToTypeMap.containsKey(strippedName)) { + return Try.success(classNameToTypeMap.get(strippedName)); } return Try.call(() -> { // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. - Matcher matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); + Matcher matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(strippedName); if (matcher.matches()) { String componentTypeName = matcher.group(1); String bracketPairs = matcher.group(2); @@ -766,7 +766,7 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } // Fallback to standard VM class loading - return Class.forName(trimmedName, false, classLoader); + return Class.forName(strippedName, false, classLoader); }); } @@ -1475,7 +1475,7 @@ public static Class[] resolveParameterTypes(Class clazz, String methodName // @formatter:off return Arrays.stream(parameterTypeNames.split(",")) - .map(String::trim) + .map(String::strip) .map(typeName -> loadRequiredParameterType(clazz, methodName, typeName)) .toArray(Class[]::new); // @formatter:on 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 5c387ad209bf..d20052a7bf2f 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 @@ -66,10 +66,11 @@ private StringUtils() { * * @param str the string to check; may be {@code null} * @return {@code true} if the string is blank + * @see String#isBlank() * @see #isNotBlank(String) */ public static boolean isBlank(@Nullable String str) { - return (str == null || str.trim().isEmpty()); + return (str == null || str.isBlank()); } /** diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java index b0e895e70dd9..117e73f3f972 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java @@ -22,6 +22,6 @@ interface DetailsPrintingListener extends TestExecutionListener { void listTests(TestPlan testPlan); static String indented(String message, String indentation) { - return LINE_START_PATTERN.matcher(message).replaceAll(indentation).trim(); + return LINE_START_PATTERN.matcher(message).replaceAll(indentation).strip(); } } 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 0a2e7002a330..51a8d874fffd 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 @@ -61,9 +61,9 @@ public final class TestTag implements Serializable { *

    *
  • A tag must not be {@code null}.
  • *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain {@linkplain #RESERVED_CHARACTERS + *
  • A stripped tag must not contain whitespace.
  • + *
  • A stripped tag must not contain ISO control characters.
  • + *
  • A stripped tag must not contain {@linkplain #RESERVED_CHARACTERS * reserved characters}.
  • *
* @@ -75,7 +75,7 @@ public final class TestTag implements Serializable { * @return {@code true} if the supplied tag name conforms to the supported * syntax for tags * @see StringUtils#isNotBlank(String) - * @see String#trim() + * @see String#strip() * @see StringUtils#doesNotContainWhitespace(String) * @see StringUtils#doesNotContainIsoControlCharacter(String) * @see #RESERVED_CHARACTERS @@ -85,7 +85,7 @@ public static boolean isValid(@Nullable String name) { if (name == null) { return false; } - name = name.trim(); + name = name.strip(); return !name.isEmpty() && // StringUtils.doesNotContainWhitespace(name) && // @@ -104,7 +104,7 @@ private static boolean doesNotContainReservedCharacter(String str) { * is {@linkplain #isValid(String) valid} before attempting to create a * {@code TestTag} using this factory method. * - *

Note: the supplied {@code name} will be {@linkplain String#trim() trimmed}. + *

Note: the supplied {@code name} will be {@linkplain String#strip() stripped}. * * @param name the name of the tag; must be syntactically valid * @throws PreconditionViolationException if the supplied tag name is not @@ -118,7 +118,7 @@ public static TestTag create(String name) throws PreconditionViolationException private TestTag(String name) { Preconditions.condition(TestTag.isValid(name), () -> "Tag name [%s] must be syntactically valid".formatted(name)); - this.name = name.trim(); + this.name = name.strip(); } /** 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 af0862827b3e..3cdb8dbf71b3 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 @@ -370,7 +370,7 @@ public static ClasspathResourceSelector selectClasspathResource(Set cl @API(status = STABLE, since = "1.10") public static ModuleSelector selectModule(String moduleName) { Preconditions.notBlank(moduleName, "Module name must not be null or blank"); - return new ModuleSelector(moduleName.trim()); + return new ModuleSelector(moduleName.strip()); } /** @@ -407,9 +407,9 @@ public static List selectModules(Set moduleNames) { */ public static PackageSelector selectPackage(String packageName) { Preconditions.notNull(packageName, "Package name must not be null"); - Preconditions.condition(packageName.isEmpty() || !packageName.trim().isEmpty(), + Preconditions.condition(packageName.isEmpty() || !packageName.isBlank(), "Package name must not contain only whitespace"); - return new PackageSelector(packageName.trim()); + return new PackageSelector(packageName.strip()); } /** @@ -594,7 +594,7 @@ public static MethodSelector selectMethod(@Nullable ClassLoader classLoader, Str Preconditions.notBlank(className, "Class name must not be null or blank"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null"); - return new MethodSelector(classLoader, className, methodName, parameterTypeNames.trim()); + return new MethodSelector(classLoader, className, methodName, parameterTypeNames.strip()); } /** @@ -629,7 +629,7 @@ public static MethodSelector selectMethod(Class javaClass, String methodName, Preconditions.notNull(javaClass, "Class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null"); - return new MethodSelector(javaClass, methodName, parameterTypeNames.trim()); + return new MethodSelector(javaClass, methodName, parameterTypeNames.strip()); } /** @@ -823,7 +823,7 @@ public static NestedMethodSelector selectNestedMethod(@Nullable ClassLoader clas Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter types must not be null"); return new NestedMethodSelector(classLoader, enclosingClassNames, nestedClassName, methodName, - parameterTypeNames.trim()); + parameterTypeNames.strip()); } /** @@ -893,7 +893,7 @@ public static NestedMethodSelector selectNestedMethod(List> enclosingCl Preconditions.notNull(nestedClass, "Nested class must not be null"); Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter types must not be null"); - return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, parameterTypeNames.trim()); + return new NestedMethodSelector(enclosingClasses, nestedClass, methodName, parameterTypeNames.strip()); } /** diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java index 6c8c7363fb86..90d7144ef2dd 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java @@ -147,7 +147,7 @@ private static List validateAndTrim(List engineIds) { // @formatter:off return engineIds.stream() - .map(id -> Preconditions.notBlank(id, "engine ID must not be null or blank").trim()) + .map(id -> Preconditions.notBlank(id, "engine ID must not be null or blank").strip()) .distinct() .toList(); // @formatter:on 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 68ccfc8be28b..953780d89125 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 @@ -168,7 +168,7 @@ private static String exclusionReasonExpressionSatisfy(List tagExpressio } private static String formatToString(List tagExpressions) { - return tagExpressions.stream().map(String::trim).sorted().collect(Collectors.joining(",")); + return tagExpressions.stream().map(String::strip).sorted().collect(Collectors.joining(",")); } private static List parseAll(List tagExpressions) { 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 cfea8896710d..be3128ed4758 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 @@ -204,7 +204,7 @@ public String toString() { static ParameterProvider propertiesFile(String configFileName) { Preconditions.notBlank(configFileName, "configFileName must not be null or blank"); - Properties properties = loadClasspathResource(configFileName.trim()); + Properties properties = loadClasspathResource(configFileName.strip()); return new ParameterProvider() { @Override public String getValue(String key) { 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 de4bbd374d12..f8cc2cadcf06 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 @@ -41,12 +41,12 @@ static ParseStatus missingRhsOperand(Token token, String representation) { static ParseStatus errorAt(Token token, String operatorRepresentation, String message) { return error( - message + " for '" + operatorRepresentation + "' at index " + format(token.trimmedTokenStartIndex())); + message + " for '" + operatorRepresentation + "' at index " + format(token.strippedTokenStartIndex())); } static ParseStatus missingOperatorBetween(TokenWith lhs, TokenWith rhs) { String lhsString = "'" + lhs.element() + "' at index " + format(lhs.token().lastCharacterIndex()); - String rhsString = "'" + rhs.element() + "' at index " + format(rhs.token().trimmedTokenStartIndex()); + String rhsString = "'" + rhs.element() + "' at index " + format(rhs.token().strippedTokenStartIndex()); return error("missing operator between " + lhsString + " and " + rhsString); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java index b239179f68e2..8ddb29748803 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java @@ -16,10 +16,10 @@ record Token(int startIndex, String rawString) { String string() { - return rawString.trim(); + return rawString.strip(); } - public int trimmedTokenStartIndex() { + public int strippedTokenStartIndex() { return startIndex + rawString.indexOf(string()); } 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 5847b73cbb7c..d9e11b931e46 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 @@ -294,7 +294,7 @@ private void collectReportEntries(TestIdentifier testIdentifier, List sy formattedReportEntries); } } - systemOutElements.add(formattedReportEntries.toString().trim()); + systemOutElements.add(formattedReportEntries.toString().strip()); systemOutElements.addAll(systemOutElementsForCapturedOutput); } } diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java index fe3e43c88903..2f4d46993f10 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java @@ -37,9 +37,9 @@ *

Syntax Rules for Tags

*
    *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain reserved characters.
  • + *
  • A stripped tag must not contain whitespace.
  • + *
  • A stripped tag must not contain ISO control characters.
  • + *
  • A stripped tag must not contain reserved characters.
  • *
* *

Reserved characters that are not permissible as part of a tag name. @@ -67,7 +67,7 @@ /** * One or more tags to exclude. * - *

Note: each tag will be {@linkplain String#trim() trimmed} and + *

Note: each tag will be {@linkplain String#strip() stripped} and * validated according to the Syntax Rules for Tags (see * {@linkplain ExcludeTags class-level Javadoc} for details). */ diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java index 2efb0137d11b..fa2a5f9ac6cf 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java @@ -37,9 +37,9 @@ *

Syntax Rules for Tags

*
    *
  • A tag must not be blank.
  • - *
  • A trimmed tag must not contain whitespace.
  • - *
  • A trimmed tag must not contain ISO control characters.
  • - *
  • A trimmed tag must not contain reserved characters.
  • + *
  • A stripped tag must not contain whitespace.
  • + *
  • A stripped tag must not contain ISO control characters.
  • + *
  • A stripped tag must not contain reserved characters.
  • *
* *

Reserved characters that are not permissible as part of a tag name. @@ -67,7 +67,7 @@ /** * One or more tags to include. * - *

Note: each tag will be {@linkplain String#trim() trimmed} and + *

Note: each tag will be {@linkplain String#strip() stripped} and * validated according to the Syntax Rules for Tags (see * {@linkplain IncludeTags class-level Javadoc} for details). */ diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java index 3688d094fe12..72e486adb196 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilder.java @@ -298,7 +298,7 @@ SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Class private void addExcludeFilters(Class suiteClass) { findAnnotationValues(suiteClass, ExcludeClassNamePatterns.class, ExcludeClassNamePatterns::value) // - .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) // + .flatMap(SuiteLauncherDiscoveryRequestBuilder::stripped) // .map(ClassNameFilter::excludeClassNamePatterns) // .ifPresent(this::filters); findAnnotationValues(suiteClass, ExcludeEngines.class, ExcludeEngines::value) // @@ -324,7 +324,7 @@ private void addClassAndMethodSelectors(Class suiteClass) { private void addIncludeFilters(Class suiteClass) { findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value) // - .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) // + .flatMap(SuiteLauncherDiscoveryRequestBuilder::stripped) // .map(this::createIncludeClassNameFilter) // .ifPresent(filters -> { this.includeClassNamePatternsUsed = true; @@ -412,11 +412,11 @@ private MethodSelector toMethodSelector(Class suiteClass, SelectMethod annota } Class type = annotation.type() == Class.class ? null : annotation.type(); - String typeName = annotation.typeName().isEmpty() ? null : annotation.typeName().trim(); + String typeName = annotation.typeName().isEmpty() ? null : annotation.typeName().strip(); String methodName = Preconditions.notBlank(annotation.name(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "method name must not be blank")); Class[] parameterTypes = annotation.parameterTypes().length == 0 ? null : annotation.parameterTypes(); - String parameterTypeNames = annotation.parameterTypeNames().trim(); + String parameterTypeNames = annotation.parameterTypeNames().strip(); if (parameterTypes != null) { Preconditions.condition(parameterTypeNames.isEmpty(), () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, @@ -482,14 +482,14 @@ private static Optional findAnnotationValues(Anno return findAnnotation(element, annotationType).map(valueExtractor).filter(values -> values.length > 0); } - private static Optional trimmed(String[] patterns) { + private static Optional stripped(String[] patterns) { if (patterns.length == 0) { return Optional.empty(); } // @formatter:off return Optional.of(Arrays.stream(patterns) .filter(StringUtils::isNotBlank) - .map(String::trim) + .map(String::strip) .toArray(String[]::new)); // @formatter:on } 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 192163ab7d05..b12e781325bb 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 @@ -102,7 +102,7 @@ public final class EngineTestKit { */ public static Builder engine(String engineId) { Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - return engine(loadTestEngine(engineId.trim())); + return engine(loadTestEngine(engineId.strip())); } /** @@ -159,7 +159,7 @@ public static Builder engine(TestEngine testEngine) { @API(status = EXPERIMENTAL, since = "6.0") public static EngineDiscoveryResults discover(String engineId, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - return discover(loadTestEngine(engineId.trim()), discoveryRequest); + return discover(loadTestEngine(engineId.strip()), discoveryRequest); } /** @@ -217,7 +217,7 @@ public static EngineDiscoveryResults discover(TestEngine testEngine, LauncherDis */ public static EngineExecutionResults execute(String engineId, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notBlank(engineId, "TestEngine ID must not be null or blank"); - return execute(loadTestEngine(engineId.trim()), discoveryRequest); + return execute(loadTestEngine(engineId.strip()), discoveryRequest); } /** 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 f8dc13677f1f..7f5ea985a705 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 @@ -730,7 +730,7 @@ void failsForLifecycleMethodWithInvalidParameters() { - parameter 'value' with index 0 is incompatible with the parameter declared on the parameterized class: expected type 'int' but found 'long' - parameter 'anotherValue' with index 1 must not be annotated with @ConvertWith """; - expectedMessage = expectedMessage.trim() // + expectedMessage = expectedMessage.strip() // .replace("\n", System.lineSeparator()); // use platform-specific line separators var failedResult = getFirstTestExecutionResult(results.containerEvents().failed()); 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 cb8aa8856663..c7f2bea9a285 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 @@ -46,7 +46,7 @@ void interceptsWriteOperationsToStreamPerThread() { .mapToObj(String::valueOf) .peek(i -> streamInterceptor.capture()) .peek(i -> targetStream.println(i)) - .forEach(i -> assertEquals(i, streamInterceptor.consume().trim())); + .forEach(i -> assertEquals(i, streamInterceptor.consume().strip())); // @formatter:on } @@ -118,6 +118,6 @@ void capturesOutputFromNonTestThreads() throws Exception { thread.start(); thread.join(); - assertEquals("from non-test thread", streamInterceptor.consume().trim()); + assertEquals("from non-test thread", streamInterceptor.consume().strip()); } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java index 46d8fd92b86e..1603c69d5785 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java @@ -18,9 +18,9 @@ class TokenTests { @Test void startIndexOfTokenString() { - assertThat(new Token(0, "!").trimmedTokenStartIndex()).isEqualTo(0); - assertThat(new Token(0, " !").trimmedTokenStartIndex()).isEqualTo(2); - assertThat(new Token(7, "!").trimmedTokenStartIndex()).isEqualTo(7); + assertThat(new Token(0, "!").strippedTokenStartIndex()).isEqualTo(0); + assertThat(new Token(0, " !").strippedTokenStartIndex()).isEqualTo(2); + assertThat(new Token(7, "!").strippedTokenStartIndex()).isEqualTo(7); } @Test diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java index 9dfb2506a38e..9b56b230f03f 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java @@ -113,9 +113,9 @@ void writesCapturedOutput() throws Exception { assertThat(testsuite.find("system-out").text(1)) // .containsSubsequence("Report Entry #1 (timestamp: ", "- foo: bar", "Report Entry #2 (timestamp: ", "- baz: qux"); - assertThat(testsuite.find("system-out").text(2).trim()) // + assertThat(testsuite.find("system-out").text(2).strip()) // .isEqualTo("normal output"); - assertThat(testsuite.find("system-err").text().trim()) // + assertThat(testsuite.find("system-err").text().strip()) // .isEqualTo("error output"); } diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java index e1bd359935ee..170243191116 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java @@ -222,7 +222,7 @@ void includesGitInfoWhenEnabled(String originUrl, @TempDir Path tempDirectory) t .valueByXPath("/e:events/core:infrastructure/git:branch") // .isEqualTo("my_branch"); - var commitHash = execGit(tempDirectory, "rev-parse", "--verify", "HEAD").stdOut().trim(); + var commitHash = execGit(tempDirectory, "rev-parse", "--verify", "HEAD").stdOut().strip(); assertThatXml(xmlFile) // .valueByXPath("/e:events/core:infrastructure/git:commit") // .isEqualTo(commitHash); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java index 94de63eef317..fae263aea6f1 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java @@ -49,8 +49,8 @@ void describeModule(String module, @FilePrefix("jar") OutputFiles outputFiles) t assertEquals("", result.stdErr(), "error log isn't empty"); var expectedLines = replaceVersionPlaceholders( - Files.readString(sourceDirectory.resolve(module + ".expected.txt")).trim()); - assertLinesMatch(expectedLines.lines().toList(), result.stdOut().trim().lines().toList()); + Files.readString(sourceDirectory.resolve(module + ".expected.txt")).strip()); + assertLinesMatch(expectedLines.lines().toList(), result.stdOut().strip().lines().toList()); } @ParameterizedTest From 495ef75d269baaf60dc21ac2a4665860e8beae8f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 30 Jun 2025 12:22:04 +0000 Subject: [PATCH 010/162] Ignore Kotlin compiler-generated `DefaultImpls` classes (#4696) Resolves #4675. --------- Co-authored-by: Sam Brannen <104798+sbrannen@users.noreply.github.com> --- .../release-notes/release-notes-5.13.3.adoc | 2 + .../junit-jupiter-api.gradle.kts | 1 + .../api/extension/DisabledInEclipse.java | 28 +++++++++ .../predicates/TestClassPredicates.java | 3 +- .../commons/util/KotlinReflectionUtils.java | 58 ++++++++++++++++++- .../util/KotlinSuspendingFunctionUtils.java | 3 +- .../commons/util/ReflectionUtils.java | 2 +- .../VintageTestEngineExecutionTests.java | 6 +- .../engine/discovery/DiscoveryTests.java | 57 ++++++++++++++++++ .../TimeoutInvocationFactoryTests.java | 6 +- .../kotlin/KotlinDefaultImplsTestCase.kt | 21 +++++++ .../KotlinInterfaceImplementationTestCase.kt | 12 ++++ .../engine/kotlin/KotlinInterfaceTestCase.kt | 19 ++++++ .../DefaultClasspathScannerTests.java | 12 +--- .../core/ClasspathAlignmentCheckerTests.java | 4 +- 15 files changed, 210 insertions(+), 24 deletions(-) create mode 100644 junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledInEclipse.java create mode 100644 jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinDefaultImplsTestCase.kt create mode 100644 jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceImplementationTestCase.kt create mode 100644 jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceTestCase.kt diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index dc6af824bf77..faadcc279e0b 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -37,6 +37,8 @@ repository on GitHub. * Stop reporting discovery issues for composed annotation classes that are meta-annotated with `@Nested`. +* Stop reporting discovery issues for `DefaultImpls` classes generated by the Kotlin + compiler for interfaces with non-abstract test methods. [[release-notes-5.13.3-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index f2592212772c..3d24a09415b9 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -18,6 +18,7 @@ dependencies { compileOnly(kotlin("stdlib")) testFixturesImplementation(libs.assertj) + testFixturesImplementation(testFixtures(projects.junitPlatformCommons)) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledInEclipse.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledInEclipse.java new file mode 100644 index 000000000000..ec93252305bb --- /dev/null +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledInEclipse.java @@ -0,0 +1,28 @@ +/* + * 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.jupiter.api.extension; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.condition.DisabledIf; + +/** + * @see org.junit.platform.commons.test.IdeUtils#runningInEclipse() + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@DisabledIf("org.junit.platform.commons.test.IdeUtils#runningInEclipse()") +public @interface DisabledInEclipse { + String value() default ""; +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java index d34f8a885d68..4867348f0acb 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java @@ -15,6 +15,7 @@ import static org.junit.platform.commons.support.ModifierSupport.isAbstract; import static org.junit.platform.commons.support.ModifierSupport.isNotAbstract; import static org.junit.platform.commons.support.ModifierSupport.isNotPrivate; +import static org.junit.platform.commons.util.KotlinReflectionUtils.isKotlinInterfaceDefaultImplsClass; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; import static org.junit.platform.commons.util.ReflectionUtils.isMethodPresent; import static org.junit.platform.commons.util.ReflectionUtils.isNestedClassPresent; @@ -74,7 +75,7 @@ public boolean looksLikeIntendedTestClass(Class candidate) { } private boolean looksLikeIntendedTestClass(Class candidate, Set> seen) { - if (seen.add(candidate)) { + if (seen.add(candidate) && !isKotlinInterfaceDefaultImplsClass(candidate)) { return this.isAnnotatedWithClassTemplate.test(candidate) // || hasTestOrTestFactoryOrTestTemplateMethods(candidate) // || hasNestedTests(candidate, seen); 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 926ae651ee99..7b1cadbaca2e 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 @@ -11,12 +11,16 @@ package org.junit.platform.commons.util; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.ReflectionUtils.EMPTY_CLASS_ARRAY; +import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.junit.platform.commons.util.ReflectionUtils.isStatic; import static org.junit.platform.commons.util.ReflectionUtils.tryToLoadClass; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.Type; +import java.util.Arrays; import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; @@ -25,15 +29,16 @@ /** * Internal Kotlin-specific reflection utilities * - * @since 6.0 + * @since 5.13.3 */ -@API(status = INTERNAL, since = "6.0") +@API(status = INTERNAL, since = "5.13.3") public class KotlinReflectionUtils { private static final @Nullable Class kotlinMetadata; private static final @Nullable Class kotlinCoroutineContinuation; private static final boolean kotlinReflectPresent; private static final boolean kotlinxCoroutinesPresent; + public static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"; static { var metadata = tryToLoadKotlinMetadataClass(); @@ -56,6 +61,10 @@ private static Try> tryToLoadKotlinMetadataClass() { .andThenTry(it -> (Class) it); } + /** + * @since 6.0 + */ + @API(status = INTERNAL, since = "6.0") public static boolean isKotlinSuspendingFunction(Method method) { if (kotlinCoroutineContinuation != null && isKotlinType(method.getDeclaringClass())) { int parameterCount = method.getParameterCount(); @@ -65,6 +74,51 @@ public static boolean isKotlinSuspendingFunction(Method method) { return false; } + /** + * Determines whether the supplied class is a {@code DefaultImpls} class + * generated by the Kotlin compiler. + * + *

See + * Kotlin documentation + * for details. + * + * @since 5.13.3 + */ + @API(status = INTERNAL, since = "5.13.3") + public static boolean isKotlinInterfaceDefaultImplsClass(Class clazz) { + if (!isKotlinType(clazz) || !DEFAULT_IMPLS_CLASS_NAME.equals(clazz.getSimpleName()) || !isStatic(clazz)) { + return false; + } + + Class enclosingClass = clazz.getEnclosingClass(); + if (enclosingClass != null && enclosingClass.isInterface()) { + return Arrays.stream(clazz.getDeclaredMethods()) // + .anyMatch(method -> isCompilerGeneratedDefaultMethod(method, enclosingClass)); + } + + return false; + } + + private static boolean isCompilerGeneratedDefaultMethod(Method method, Class enclosingClass) { + if (isStatic(method) && method.getParameterCount() > 0) { + var parameterTypes = method.getParameterTypes(); + if (parameterTypes[0] == enclosingClass) { + var originalParameterTypes = copyWithoutFirst(parameterTypes); + return findMethod(enclosingClass, method.getName(), originalParameterTypes).isPresent(); + } + } + return false; + } + + private static Class[] copyWithoutFirst(Class[] values) { + if (values.length == 1) { + return EMPTY_CLASS_ARRAY; + } + var result = new Class[values.length - 1]; + System.arraycopy(values, 1, result, 0, result.length); + return result; + } + private static boolean isKotlinType(Class clazz) { return kotlinMetadata != null // && clazz.getDeclaredAnnotation(kotlinMetadata) != null; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java index 363af7fcffc3..f2b186aa575f 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java @@ -18,6 +18,7 @@ import static kotlin.reflect.jvm.ReflectJvmMapping.getJavaType; import static kotlinx.coroutines.BuildersKt.runBlocking; import static org.junit.platform.commons.util.ExceptionUtils.throwAsUncheckedException; +import static org.junit.platform.commons.util.ReflectionUtils.EMPTY_CLASS_ARRAY; import static org.junit.platform.commons.util.ReflectionUtils.getUnderlyingCause; import java.lang.reflect.Method; @@ -60,7 +61,7 @@ static Parameter[] getParameters(Method method) { static Class[] getParameterTypes(Method method) { var parameterCount = method.getParameterCount(); if (parameterCount == 1) { - return new Class[0]; + return EMPTY_CLASS_ARRAY; } return Arrays.stream(method.getParameterTypes()).limit(parameterCount - 1).toArray(Class[]::new); } 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 1364dead4875..103b8f2fd234 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 @@ -113,7 +113,7 @@ public enum HierarchyTraversalMode { // ++ => possessive quantifier private static final Pattern SOURCE_CODE_SYNTAX_ARRAY_PATTERN = Pattern.compile("^([^\\[\\]]+)((?>\\[\\])++)$"); - private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; private static final ClasspathScanner classpathScanner = ClasspathScannerLoader.getInstance(); diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index 356b2b95bf30..7044e3f5076a 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -42,7 +42,7 @@ import org.assertj.core.api.Condition; import org.junit.AssumptionViolatedException; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; @@ -865,7 +865,7 @@ void executesJUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions() { } @Test - @DisabledIf("org.junit.platform.commons.test.IdeUtils#runningInEclipse()") + @DisabledInEclipse void executesUnrolledSpockFeatureMethod() { // Load Groovy class via reflection to avoid compilation errors in Eclipse IDE. String testClassName = "org.junit.vintage.engine.samples.spock.SpockTestCaseWithUnrolledAndRegularFeatureMethods"; @@ -888,7 +888,7 @@ void executesUnrolledSpockFeatureMethod() { } @Test - @DisabledIf("org.junit.platform.commons.test.IdeUtils#runningInEclipse()") + @DisabledInEclipse void executesRegularSpockFeatureMethod() { // Load Groovy class via reflection to avoid compilation errors in Eclipse IDE. String testClassName = "org.junit.vintage.engine.samples.spock.SpockTestCaseWithUnrolledAndRegularFeatureMethods"; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java index 2cb8532f3cef..045e4be726f7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -14,9 +14,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; import static org.junit.jupiter.params.provider.Arguments.argumentSet; +import static org.junit.platform.commons.test.IdeUtils.runningInEclipse; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -39,6 +41,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; @@ -47,6 +50,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; @@ -74,6 +78,50 @@ void doNotDiscoverAbstractTestClass() { assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } + @ParameterizedTest + @ValueSource(strings = { "org.junit.jupiter.engine.discovery.DiscoveryTests$InterfaceTestCase", + "org.junit.jupiter.engine.kotlin.KotlinInterfaceTestCase" }) + void doNotDiscoverTestInterface(String className) { + + assumeFalse(runningInEclipse() && className.contains(".kotlin.")); + + LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(className)).build(); + TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); + assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + @DisabledInEclipse + void doNotDiscoverGeneratedKotlinDefaultImplsClass() { + LauncherDiscoveryRequest request = defaultRequest() // + .selectors(selectClass("org.junit.jupiter.engine.kotlin.KotlinInterfaceTestCase$DefaultImpls")) // + .build(); + TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); + assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @Test + @DisabledInEclipse + void discoverDeclaredKotlinDefaultImplsClass() { + LauncherDiscoveryRequest request = defaultRequest().selectors( + selectClass("org.junit.jupiter.engine.kotlin.KotlinDefaultImplsTestCase$DefaultImpls")).build(); + TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + + @ParameterizedTest + @ValueSource(strings = { + "org.junit.jupiter.engine.discovery.DiscoveryTests$ConcreteImplementationOfInterfaceTestCase", + "org.junit.jupiter.engine.kotlin.KotlinInterfaceImplementationTestCase" }) + void discoverTestClassInheritingTestsFromInterface(String className) { + + assumeFalse(runningInEclipse() && className.contains(".kotlin.")); + + LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(className)).build(); + TestDescriptor engineDescriptor = discoverTestsWithoutIssues(request); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + } + @Test void discoverMethodByUniqueId() { LauncherDiscoveryRequest request = defaultRequest().selectors( @@ -524,4 +572,13 @@ void test() { } } + interface InterfaceTestCase { + @Test + default void test() { + } + } + + static class ConcreteImplementationOfInterfaceTestCase implements InterfaceTestCase { + } + } 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 a1544c3843dc..7e6e6e4f0d2a 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 @@ -18,7 +18,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout.ThreadMode; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; @@ -35,9 +35,7 @@ // Mockito cannot mock this class: class org.junit.jupiter.engine.execution.NamespaceAwareStore. // You are seeing this disclaimer because Mockito is configured to create inlined mocks. // Byte Buddy could not instrument all classes within the mock's type hierarchy. -@DisabledIf(// - value = "org.junit.platform.commons.test.IdeUtils#runningInEclipse()", // - disabledReason = "Mockito cannot create a spy for NamespaceAwareStore using the inline MockMaker in Eclipse IDE") +@DisabledInEclipse("Mockito cannot create a spy for NamespaceAwareStore using the inline MockMaker in Eclipse IDE") @DisplayName("TimeoutInvocationFactory") @ExtendWith(MockitoExtension.class) class TimeoutInvocationFactoryTests { diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinDefaultImplsTestCase.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinDefaultImplsTestCase.kt new file mode 100644 index 000000000000..90b97bd67e07 --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinDefaultImplsTestCase.kt @@ -0,0 +1,21 @@ +/* + * 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.jupiter.engine.kotlin + +import org.junit.jupiter.api.Test + +class KotlinDefaultImplsTestCase { + @Suppress("JUnitMalformedDeclaration") + class DefaultImpls { + @Test + fun test() { + } + } +} diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceImplementationTestCase.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceImplementationTestCase.kt new file mode 100644 index 000000000000..82be8a13cae1 --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceImplementationTestCase.kt @@ -0,0 +1,12 @@ +/* + * 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.jupiter.engine.kotlin + +class KotlinInterfaceImplementationTestCase : KotlinInterfaceTestCase diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceTestCase.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceTestCase.kt new file mode 100644 index 000000000000..1df74af930ca --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/KotlinInterfaceTestCase.kt @@ -0,0 +1,19 @@ +/* + * 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.jupiter.engine.kotlin + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInfo + +interface KotlinInterfaceTestCase { + @Test + fun test(testInfo: TestInfo) { + } +} 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 c2515661ffe0..d79540ddf728 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 @@ -42,7 +42,7 @@ import java.util.spi.ToolProvider; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.commons.PreconditionViolationException; @@ -301,7 +301,7 @@ void scanForResourcesInPackage() { } @Test // #2500 - @DisabledIf("runningInEclipse") + @DisabledInEclipse void scanForClassesInPackageWithinModulesSharingNamePrefix(@TempDir Path temp) throws Exception { var moduleSourcePath = Path.of(getClass().getResource("/modules-2500/").toURI()).toString(); run("javac", "--module", "foo,foo.bar", "--module-source-path", moduleSourcePath, "-d", temp.toString()); @@ -630,12 +630,4 @@ public Enumeration getResources(String name) throws IOException { } } - /** - * Determine if the current code is running in the Eclipse IDE. - */ - static boolean runningInEclipse() { - return StackWalker.getInstance().walk( - stream -> stream.anyMatch(stackFrame -> stackFrame.getClassName().startsWith("org.eclipse.jdt"))); - } - } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java index bc2497202056..ec6417ccddf8 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java @@ -25,7 +25,7 @@ import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.extension.DisabledInEclipse; class ClasspathAlignmentCheckerTests { @@ -60,7 +60,7 @@ void wrapsLinkageErrorForUnalignedClasspath() { } @Test - @DisabledIf("org.junit.platform.commons.test.IdeUtils#runningInEclipse()") + @DisabledInEclipse void allRootPackagesAreChecked() { var allowedFileNames = Pattern.compile("junit-(?:platform|jupiter|vintage)-.+[\\d.]+(?:-SNAPSHOT)?\\.jar"); var classGraph = new ClassGraph() // From d8c79a80b44e39fab977a4d4f39962010367eb97 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 30 Jun 2025 14:41:14 +0200 Subject: [PATCH 011/162] Make internal constant private --- .../org/junit/platform/commons/util/KotlinReflectionUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 7b1cadbaca2e..f27ba3c1907f 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 @@ -34,11 +34,12 @@ @API(status = INTERNAL, since = "5.13.3") public class KotlinReflectionUtils { + private static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"; + private static final @Nullable Class kotlinMetadata; private static final @Nullable Class kotlinCoroutineContinuation; private static final boolean kotlinReflectPresent; private static final boolean kotlinxCoroutinesPresent; - public static final String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"; static { var metadata = tryToLoadKotlinMetadataClass(); From 9db136fd2ad648986cc1c48efb0733dd03913592 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 30 Jun 2025 12:58:03 +0000 Subject: [PATCH 012/162] Use `pull_request_target` so action works for contributor PRs --- .github/workflows/label-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/label-pull-request.yml b/.github/workflows/label-pull-request.yml index 8f3807caad7f..c675d52bd3e7 100644 --- a/.github/workflows/label-pull-request.yml +++ b/.github/workflows/label-pull-request.yml @@ -1,6 +1,6 @@ name: Copy labels from linked issues to PR on: - pull_request: + pull_request_target: types: [opened, reopened, edited, synchronize] permissions: {} jobs: From f8513cbd8b867f0c4252d4d9625d77edc45820cb Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 30 Jun 2025 15:08:52 +0200 Subject: [PATCH 013/162] Document that extra chars after closing quote are no longer allowed (#4694) Resolves #4693. --- .../docs/asciidoc/release-notes/release-notes-6.0.0-M1.adoc | 4 ++++ 1 file changed, 4 insertions(+) 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 0c8e2d067e65..f84261f70951 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 @@ -120,6 +120,10 @@ repository on GitHub. * Attributes such as `ignoreLeadingAndTrailingWhitespace`, `nullValues`, and others in `@CsvSource` and `@CsvFileSource` now apply to header fields as well as to regular fields. +* Extra characters after a closing quote are no longer allowed in `@CsvSource` and + `@CsvFileSource`. For example, if a single quote is used as the quote character, + the following CSV value `'foo'INVALID,'bar'` will now cause an exception to be thrown. + This helps ensure that malformed input is not silently accepted or misinterpreted. * The `junit-jupiter-migrationsupport` artifact and its contained classes are now deprecated and will be removed in the next major version. * The type bounds of the following methods have been changed to be more flexible and allow From 2a52a0643fba15b52fcf13dd553758fcbc1d0458 Mon Sep 17 00:00:00 2001 From: Vladimir Dmitrienko Date: Mon, 30 Jun 2025 17:46:56 +0200 Subject: [PATCH 014/162] Restore compatibility in whitespace detection (#4692) `String.trim()` is now used consistently. See https://github.com/junit-team/junit-framework/issues/3824#issuecomment-3016600161 for details. --- .../junit/jupiter/params/provider/CsvFileSource.java | 3 +++ .../jupiter/params/provider/CsvReaderFactory.java | 2 +- .../org/junit/jupiter/params/provider/CsvSource.java | 3 +++ .../params/provider/CsvArgumentsProviderTests.java | 11 +++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java index b51f27b61637..28a1bf469c77 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java @@ -225,6 +225,9 @@ * Controls whether leading and trailing whitespace characters of unquoted * CSV columns should be ignored. * + *

Whitespace refers to characters with Unicode code points less than + * or equal to {@code U+0020}, as defined by {@link String#trim()}. + * *

Defaults to {@code true}. * * @since 5.8 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 24420b9eef51..004982e7ab02 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -165,7 +165,7 @@ public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean if (!quoted && field.isBlank()) { return NULL_MARKER; } - String modifiedField = (!quoted && ignoreLeadingAndTrailingWhitespaces) ? field.strip() : field; + String modifiedField = (!quoted && ignoreLeadingAndTrailingWhitespaces) ? field.trim() : field; if (nullValues.contains(modifiedField)) { return NULL_MARKER; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java index 295add81b394..b8dd46cd5b61 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -286,6 +286,9 @@ * Controls whether leading and trailing whitespace characters of unquoted * CSV columns should be ignored. * + *

Whitespace refers to characters with Unicode code points less than + * or equal to {@code U+0020}, as defined by {@link String#trim()}. + * *

Defaults to {@code true}. * * @since 5.8 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 4e6eab77eec9..7fb60bc08063 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 @@ -126,6 +126,17 @@ void trimsTrailingSpaces() { assertThat(arguments).containsExactly(new Object[][] { { "1", "" }, { "2", "" }, { "3", "" }, { "4", "" } }); } + @Test + void trimsSpacesUsingStringTrim() { + // \u0000 (null) removed by trim(), preserved by strip() + // \u00A0 (non-breaking space) preserved by trim(), removed by strip() + var annotation = csvSource().lines("\u0000foo,\u00A0bar", "\u0000' foo',\u00A0' bar'").build(); + + var arguments = provideArguments(annotation); + + assertThat(arguments).containsExactly(array("foo", "\u00A0bar"), array(" foo", "\u00A0' bar'")); + } + @Test void ignoresLeadingAndTrailingSpaces() { var annotation = csvSource().lines("1,a", "2, b", "3,c ", "4, d ") // From 3026c6272d11115728a15cfd104055310a694d52 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 30 Jun 2025 18:09:18 +0200 Subject: [PATCH 015/162] Test status quo for ConditionEvaluationResult and its factory methods Prior to this commit, we did not have any "unit tests" for ConditionEvaluationResult, and while working on #4698 I noticed that we in fact have several issues in the implementation of and documentation for ConditionEvaluationResult. Thus, this commit introduces dedicated unit tests for the status quo, where individual TODOs will be addressed in separate issues/commits. See #4698 See https://github.com/junit-team/junit-framework/pull/4699#issuecomment-3018929461 --- .../ConditionEvaluationResultTests.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java new file mode 100644 index 000000000000..d4949cbef155 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -0,0 +1,165 @@ +/* + * 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.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * Unit tests for {@link ConditionEvaluationResult}. + * + * @since 5.13.3 + */ +class ConditionEvaluationResultTests { + + @Test + void enabledWithReason() { + var result = ConditionEvaluationResult.enabled("reason"); + + assertThat(result.isDisabled()).isFalse(); + assertThat(result.getReason()).contains("reason"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = true, reason = 'reason']"); + } + + @EmptyReasonsTest + void enabledWithInvalidReason(@Nullable String reason) { + @SuppressWarnings("NullAway") + var result = ConditionEvaluationResult.enabled(reason); + + assertThat(result.isDisabled()).isFalse(); + + if (reason == null) { + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = true, reason = '']"); + } + // TODO Remove else-block once issues are addressed. + else { + assertThat(result.getReason()).contains(reason); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = true, reason = '%s']", reason); + } + } + + @Test + void disabledWithDefaultReason() { + var result = ConditionEvaluationResult.disabled("default"); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).contains("default"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); + } + + @EmptyReasonsTest + void disabledWithInvalidDefaultReason(@Nullable String reason) { + @SuppressWarnings("NullAway") + var result = ConditionEvaluationResult.disabled(reason); + + assertThat(result.isDisabled()).isTrue(); + + if (reason == null) { + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); + } + // TODO Remove else-block once issues are addressed. + else { + assertThat(result.getReason()).contains(reason); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '%s']", reason); + } + } + + @EmptyReasonsTest + void disabledWithValidDefaultReasonAndInvalidCustomReason(@Nullable String customReason) { + @SuppressWarnings("NullAway") + var result = ConditionEvaluationResult.disabled("default", customReason); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).contains("default"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); + } + + @EmptyReasonsTest + void disabledWithInvalidDefaultReasonAndValidCustomReason(@Nullable String reason) { + @SuppressWarnings("NullAway") + var result = ConditionEvaluationResult.disabled(reason, "custom"); + + assertThat(result.isDisabled()).isTrue(); + + // TODO Convert to single assertion once issues are addressed. + // The following should hold for all null/blank default reasons. + // assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'custom']"); + + if (reason == null) { + assertThat(result.getReason()).contains("null ==> custom"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'null ==> custom']"); + } + else { + var generatedReason = reason + " ==> custom"; + assertThat(result.getReason()).contains(generatedReason); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '%s']", generatedReason); + } + } + + @EmptyReasonsTest + void disabledWithInvalidDefaultReasonAndInvalidCustomReason(@Nullable String reason) { + // We intentionally use the reason as both the default and custom reason. + @SuppressWarnings("NullAway") + var result = ConditionEvaluationResult.disabled(reason, reason); + + assertThat(result.isDisabled()).isTrue(); + + if (reason == null) { + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); + } + // TODO Remove else-block once issues are addressed. + else { + assertThat(result.getReason()).contains(reason); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '%s']", reason); + } + } + + @Test + void disabledWithValidDefaultReasonAndCustomReason() { + var result = ConditionEvaluationResult.disabled("default", "custom"); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).contains("default ==> custom"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default ==> custom']"); + } + + @Retention(RetentionPolicy.RUNTIME) + @ParameterizedTest + @NullSource + @ValueSource(strings = { "", " ", " ", "\t", "\n" }) + @interface EmptyReasonsTest { + } + +} From 36a1faf1120b973bbf92a475f93c9a447301d06e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:47:59 +0000 Subject: [PATCH 016/162] Update github/codeql-action action to v3.29.2 --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 18f07c8a298e..604c2ac2ff69 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -53,6 +53,6 @@ jobs: -Dscan.tag.CodeQL \ classes - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 67105a0d426c..59734595a725 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: sarif_file: results.sarif From 617a48fd07e2f9090bb327ea6325032e6db0e779 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 1 Jul 2025 07:57:07 +0200 Subject: [PATCH 017/162] Update 5.13.2 release notes to mention fix for CVE-2025-53103 --- .../release-notes/release-notes-5.13.2.adoc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc index a025895f3e20..9393b85128b8 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc @@ -13,6 +13,19 @@ repository on GitHub. [[release-notes-5.13.2-junit-platform]] === JUnit Platform +[[release-notes-5.13.2-junit-platform-bug-fixes]] +==== Bug Fixes + +* If Git information is included in the Open Test Reporting XML format (see below), any + credentials that may be configured as part the `remote.origin.url` setting in Git were + written to the `originUrl` attribute of `` elements. For example, when + cloning a GitHub repository using a URL like + `https://username:password@github.com/organization/repository.git` both username and + password were included in the XML report. Since this report may be shared, published, or + archived (for example, on a CI server) while including this information, this was + reported as a potential security vulnerability (CVE-2025-53103). Any credentials are now + being replaced with `\***` before writing them to the XML report. + [[release-notes-5.13.2-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes From 01abb5398cccd283fe898cfc43c710e0e081c6d7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 23:14:46 +0000 Subject: [PATCH 018/162] Update plugin jreleaser to v1.19.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 431cc7c66a50..dd48ed95abf7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -103,7 +103,7 @@ gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" } jmh = { id = "me.champeau.jmh", version = "0.7.3" } # Remove `sshj` constraint in gradle/plugins/publishing/build.gradle.kts when updating # see https://github.com/jreleaser/jreleaser/issues/1900 -jreleaser = { id = "org.jreleaser", version = "1.18.0" } +jreleaser = { id = "org.jreleaser", version = "1.19.0" } # check if workaround in gradle.properties can be removed when updating kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } From cc9b91986f919dbdd34d9f62957a8f42a466c69d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 1 Jul 2025 08:35:58 +0200 Subject: [PATCH 019/162] Remove obsolete sshj constraint --- gradle/libs.versions.toml | 2 -- gradle/plugins/publishing/build.gradle.kts | 6 ------ 2 files changed, 8 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dd48ed95abf7..1f20769f287e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -101,8 +101,6 @@ errorProne = { id = "net.ltgt.errorprone", version = "4.3.0" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" } jmh = { id = "me.champeau.jmh", version = "0.7.3" } -# Remove `sshj` constraint in gradle/plugins/publishing/build.gradle.kts when updating -# see https://github.com/jreleaser/jreleaser/issues/1900 jreleaser = { id = "org.jreleaser", version = "1.19.0" } # check if workaround in gradle.properties can be removed when updating kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } diff --git a/gradle/plugins/publishing/build.gradle.kts b/gradle/plugins/publishing/build.gradle.kts index a52228904a92..67f12269c158 100644 --- a/gradle/plugins/publishing/build.gradle.kts +++ b/gradle/plugins/publishing/build.gradle.kts @@ -9,12 +9,6 @@ dependencies { implementation("junitbuild.base:dsl-extensions") implementation(libs.plugins.jreleaser.markerCoordinates) constraints { - implementation("com.hierynomus:sshj") { - version { - require("0.40.0") - } - because("Workaround for CVE-2020-36843") - } implementation("org.eclipse.jgit:org.eclipse.jgit") { version { require("6.10.1.202505221210-r") From 442aac18c995e097850108df1a0eeb0b2c6b8648 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 1 Jul 2025 08:22:37 +0000 Subject: [PATCH 020/162] Run non-inner `@Nested` classes to restore backward compatibility (#4700) Since the introduction of discovery issues in 5.13, top-level and static member classes annotated with `@Nested` were no longer executed because the validation logic excluded them. This commit restores the old behavior and improves the generated discovery issues for such cases. Fixes #4686. --- .../discovery/ClassSelectorResolver.java | 14 ++++++- .../predicates/TestClassPredicates.java | 40 ++++++++++++++----- .../engine/NestedTestClassesTests.java | 34 ++++++++++++++++ .../jupiter/engine/StaticNestedTestCase.java | 26 ++++++++++++ .../engine/TopLevelNestedTestCase.java | 22 ++++++++++ .../engine/discovery/DiscoveryTests.java | 4 +- .../predicates/TestClassPredicatesTests.java | 13 ++++-- 7 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedTestCase.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/engine/TopLevelNestedTestCase.java diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 3294a1757a05..3515a5cfabd7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -15,6 +15,7 @@ import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; +import static org.junit.jupiter.engine.discovery.predicates.TestClassPredicates.NestedClassInvalidityReason.NOT_INNER; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.util.FunctionUtils.where; @@ -86,13 +87,22 @@ public Resolution resolve(ClassSelector selector, Context context) { if (this.predicates.isAnnotatedWithNested.test(testClass)) { // Class name filter is not applied to nested test classes - if (this.predicates.isValidNestedTestClass(testClass)) { + var invalidityReason = this.predicates.validateNestedTestClass(testClass); + if (invalidityReason == null) { return toResolution( context.addToParent(() -> DiscoverySelectors.selectClass(testClass.getEnclosingClass()), parent -> Optional.of(newMemberClassTestDescriptor(parent, testClass)))); } + if (invalidityReason == NOT_INNER) { + return resolveStandaloneTestClass(context, testClass); + } + return unresolved(); } - else if (isAcceptedStandaloneTestClass(testClass)) { + return resolveStandaloneTestClass(context, testClass); + } + + private Resolution resolveStandaloneTestClass(Context context, Class testClass) { + if (isAcceptedStandaloneTestClass(testClass)) { return toResolution( context.addToParent(parent -> Optional.of(newStandaloneClassTestDescriptor(parent, testClass)))); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java index 4867348f0acb..f44347380446 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java @@ -27,6 +27,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.ClassTemplate; import org.junit.jupiter.api.Nested; import org.junit.platform.commons.util.ReflectionUtils; @@ -55,15 +56,16 @@ public class TestClassPredicates { candidate) || looksLikeIntendedTestClass(candidate); public final Predicate isTestOrTestFactoryOrTestTemplateMethod; - private final Condition> isValidNestedTestClass; + private final Condition> isNotPrivateUnlessAbstractNestedClass; + private final Condition> isInnerNestedClass; private final Condition> isValidStandaloneTestClass; public TestClassPredicates(DiscoveryIssueReporter issueReporter) { this.isTestOrTestFactoryOrTestTemplateMethod = new IsTestMethod(issueReporter) // .or(new IsTestFactoryMethod(issueReporter)) // .or(new IsTestTemplateMethod(issueReporter)); - this.isValidNestedTestClass = isNotPrivateUnlessAbstract("@Nested", issueReporter) // - .and(isInner(issueReporter)); + this.isNotPrivateUnlessAbstractNestedClass = isNotPrivateUnlessAbstract("@Nested", issueReporter); + this.isInnerNestedClass = isInner(issueReporter); this.isValidStandaloneTestClass = isNotPrivateUnlessAbstract("Test", issueReporter) // .and(isNotLocal(issueReporter)) // .and(isNotInnerUnlessAbstract(issueReporter)) // @@ -84,8 +86,16 @@ private boolean looksLikeIntendedTestClass(Class candidate, Set> see } public boolean isValidNestedTestClass(Class candidate) { - return this.isValidNestedTestClass.check(candidate) // - && isNotAbstract(candidate); + return validateNestedTestClass(candidate) == null; + } + + public @Nullable NestedClassInvalidityReason validateNestedTestClass(Class candidate) { + boolean isInner = this.isInnerNestedClass.check(candidate); + boolean isNotPrivateUnlessAbstract = this.isNotPrivateUnlessAbstractNestedClass.check(candidate); + if (isNotPrivateUnlessAbstract && isNotAbstract(candidate)) { + return isInner ? null : NestedClassInvalidityReason.NOT_INNER; + } + return NestedClassInvalidityReason.OTHER; } public boolean isValidStandaloneTestClass(Class candidate) { @@ -124,9 +134,13 @@ private static Condition> isNotLocal(DiscoveryIssueReporter issueReport private static Condition> isInner(DiscoveryIssueReporter issueReporter) { return issueReporter.createReportingCondition(ReflectionUtils::isInnerClass, testClass -> { if (testClass.getEnclosingClass() == null) { - return createIssue("@Nested", testClass, "must not be a top-level class"); + return createIssue("Top-level", testClass, "must not be annotated with @Nested", + "It will be executed anyway for backward compatibility. " + + "You should remove the @Nested annotation to resolve this warning."); } - return createIssue("@Nested", testClass, "must not be static"); + return createIssue("@Nested", testClass, "must not be static", + "It will only be executed if discovered as a standalone test class. " + + "You should remove the annotation or make it non-static to resolve this warning."); }); } @@ -141,8 +155,11 @@ private static Condition> isNotAnonymous(DiscoveryIssueReporter issueRe } private static DiscoveryIssue createIssue(String prefix, Class testClass, String detailMessage) { - String message = "%s class '%s' %s. It will not be executed.".formatted(prefix, testClass.getName(), - detailMessage); + return createIssue(prefix, testClass, detailMessage, "It will not be executed."); + } + + private static DiscoveryIssue createIssue(String prefix, Class testClass, String detailMessage, String effect) { + String message = "%s class '%s' %s. %s".formatted(prefix, testClass.getName(), detailMessage, effect); return DiscoveryIssue.builder(DiscoveryIssue.Severity.WARNING, message) // .source(ClassSource.from(testClass)) // .build(); @@ -151,4 +168,9 @@ private static DiscoveryIssue createIssue(String prefix, Class testClass, Str private static boolean isAnnotatedButNotComposed(Class candidate, Class annotationType) { return !candidate.isAnnotation() && isAnnotated(candidate, annotationType); } + + @API(status = INTERNAL, since = "5.13.3") + public enum NestedClassInvalidityReason { + NOT_INNER, OTHER + } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java index 68bd39c89366..70a844cd2639 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java @@ -274,6 +274,40 @@ private void assertNestedCycle(Class start, Class from, Class to) { .haveExactly(1, finishedWithFailure(message(it -> it.contains(expectedMessage)))); } + @Test + void discoversButWarnsAboutTopLevelNestedTestClasses() { + var results = discoverTestsForClass(TopLevelNestedTestCase.class); + + var engineDescriptor = results.getEngineDescriptor(); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + + var discoveryIssues = results.getDiscoveryIssues(); + assertThat(discoveryIssues).hasSize(1); + assertThat(discoveryIssues.getFirst().message()) // + .isEqualTo( + "Top-level class '%s' must not be annotated with @Nested. " + + "It will be executed anyway for backward compatibility. " + + "You should remove the @Nested annotation to resolve this warning.", + TopLevelNestedTestCase.class.getName()); + } + + @Test + void discoversButWarnsAboutStaticNestedTestClasses() { + var results = discoverTestsForClass(StaticNestedTestCase.TestCase.class); + + var engineDescriptor = results.getEngineDescriptor(); + assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); + + var discoveryIssues = results.getDiscoveryIssues(); + assertThat(discoveryIssues).hasSize(1); + assertThat(discoveryIssues.getFirst().message()) // + .isEqualTo( + "@Nested class '%s' must not be static. " + + "It will only be executed if discovered as a standalone test class. " + + "You should remove the annotation or make it non-static to resolve this warning.", + StaticNestedTestCase.TestCase.class.getName()); + } + // ------------------------------------------------------------------- @SuppressWarnings("JUnitMalformedDeclaration") diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedTestCase.java new file mode 100644 index 000000000000..a68851a90ada --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedTestCase.java @@ -0,0 +1,26 @@ +/* + * 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.jupiter.engine; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class StaticNestedTestCase { + + @SuppressWarnings("JUnitMalformedDeclaration") + @Nested + static class TestCase { + @Test + void test() { + } + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TopLevelNestedTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TopLevelNestedTestCase.java new file mode 100644 index 000000000000..da2b30026e9e --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TopLevelNestedTestCase.java @@ -0,0 +1,22 @@ +/* + * 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.jupiter.engine; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@Nested +public class TopLevelNestedTestCase { + + @Test + void test() { + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java index 045e4be726f7..02fa1eb5b01d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -305,8 +305,8 @@ void reportsWarningForInvalidNestedTestClass(LauncherDiscoveryRequest request) { .isEqualTo("@Nested class '%s' must not be private. It will not be executed.", InvalidTestCases.InvalidTestClassTestCase.Inner.class.getName()); assertThat(discoveryIssues.getLast().message()) // - .isEqualTo("@Nested class '%s' must not be static. It will not be executed.", - InvalidTestCases.InvalidTestClassTestCase.Inner.class.getName()); + .startsWith("@Nested class '%s' must not be static.".formatted( + InvalidTestCases.InvalidTestClassTestCase.Inner.class.getName())); } static List> requestsForTestClassWithInvalidNestedTestClass() { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicatesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicatesTests.java index bacd663d33df..34d5f9ae541a 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicatesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicatesTests.java @@ -254,7 +254,9 @@ void staticNestedClassEvaluatesToFalse() { assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, - "@Nested class '%s' must not be static. It will not be executed.".formatted(candidate.getName())) // + "@Nested class '%s' must not be static. ".formatted(candidate.getName()) + + "It will only be executed if discovered as a standalone test class. " + + "You should remove the annotation or make it non-static to resolve this warning.") // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); @@ -268,8 +270,9 @@ void topLevelClassEvaluatesToFalse() { assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, - "@Nested class '%s' must not be a top-level class. It will not be executed.".formatted( - candidate.getName())) // + ("Top-level class '%s' must not be annotated with @Nested. ".formatted(candidate.getName()) + + "It will be executed anyway for backward compatibility. " + + "You should remove the @Nested annotation to resolve this warning.")) // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); @@ -312,7 +315,9 @@ class LocalClass { assertThat(predicates.isAnnotatedWithNestedAndValid).rejects(candidate); var issue = DiscoveryIssue.builder(Severity.WARNING, - "@Nested class '%s' must not be static. It will not be executed.".formatted(candidate.getName())) // + "@Nested class '%s' must not be static. ".formatted(candidate.getName()) + + "It will only be executed if discovered as a standalone test class. " + + "You should remove the annotation or make it non-static to resolve this warning.") // .source(ClassSource.from(candidate)) // .build(); assertThat(discoveryIssues.stream().distinct()).containsExactly(issue); From 7dc279167e80b20aab3432a393cf98cb7e42e2f4 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 1 Jul 2025 08:45:26 +0000 Subject: [PATCH 021/162] Consistently declare JSpecify as a transitive compile-time dependency (#4703) Prior to this commit this was only done in `junit-platform-commons` which all other modules depend on. Therefore, JSpecify was already a transitive compile-time dependency of all modules but their module descriptors listed it as an non-transitive compile-time dependency. --- documentation/documentation.gradle.kts | 2 ++ junit-jupiter-api/junit-jupiter-api.gradle.kts | 2 +- junit-jupiter-api/src/main/java/module-info.java | 2 +- junit-jupiter-engine/junit-jupiter-engine.gradle.kts | 2 +- junit-jupiter-engine/src/main/java/module-info.java | 2 +- .../junit-jupiter-migrationsupport.gradle.kts | 2 +- junit-jupiter-migrationsupport/src/main/java/module-info.java | 2 +- junit-jupiter-params/junit-jupiter-params.gradle.kts | 2 +- junit-jupiter-params/src/main/java/module-info.java | 2 +- junit-platform-console/junit-platform-console.gradle.kts | 2 +- junit-platform-console/src/main/java/module-info.java | 2 +- junit-platform-engine/src/main/java/module-info.java | 2 +- junit-platform-launcher/junit-platform-launcher.gradle.kts | 2 +- junit-platform-launcher/src/main/java/module-info.java | 2 +- junit-platform-reporting/junit-platform-reporting.gradle.kts | 2 +- junit-platform-reporting/src/main/java/module-info.java | 2 +- junit-platform-suite-api/junit-platform-suite-api.gradle.kts | 2 +- junit-platform-suite-api/src/main/java/module-info.java | 2 +- .../junit-platform-suite-engine.gradle.kts | 2 +- junit-platform-suite-engine/src/main/java/module-info.java | 2 +- junit-platform-testkit/junit-platform-testkit.gradle.kts | 2 +- junit-platform-testkit/src/main/java/module-info.java | 2 +- junit-vintage-engine/junit-vintage-engine.gradle.kts | 2 +- junit-vintage-engine/src/main/java/module-info.java | 2 +- .../projects/jar-describe-module/junit-jupiter-api.expected.txt | 2 +- .../jar-describe-module/junit-jupiter-engine.expected.txt | 2 +- .../junit-jupiter-migrationsupport.expected.txt | 2 +- .../jar-describe-module/junit-jupiter-params.expected.txt | 2 +- .../jar-describe-module/junit-platform-console.expected.txt | 2 +- .../jar-describe-module/junit-platform-engine.expected.txt | 2 +- .../jar-describe-module/junit-platform-launcher.expected.txt | 2 +- .../jar-describe-module/junit-platform-reporting.expected.txt | 2 +- .../jar-describe-module/junit-platform-suite-api.expected.txt | 2 +- .../junit-platform-suite-engine.expected.txt | 2 +- .../jar-describe-module/junit-platform-testkit.expected.txt | 2 +- .../jar-describe-module/junit-vintage-engine.expected.txt | 2 +- 36 files changed, 37 insertions(+), 35 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 4a10e582fcd5..3fc54f95b223 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -55,6 +55,8 @@ dependencies { because("Jupiter API is used in src/main/java") } + compileOnlyApi(libs.jspecify) + // Pull in all "modular projects" to ensure that they are included // in reports generated by the ApiReportGenerator. modularProjects.forEach { apiReport(it) } diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index 3d24a09415b9..be4e103bada2 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -13,7 +13,7 @@ dependencies { api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(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 2660f24f0d87..c41a935d2629 100644 --- a/junit-jupiter-api/src/main/java/module-info.java +++ b/junit-jupiter-api/src/main/java/module-info.java @@ -16,7 +16,7 @@ module org.junit.jupiter.api { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires transitive org.junit.platform.commons; requires transitive org.opentest4j; diff --git a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts index 7b3e9afcd3e7..9bfee54182cc 100644 --- a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts +++ b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts @@ -12,7 +12,7 @@ dependencies { api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(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 ac95e98aeb2f..14badf21612c 100644 --- a/junit-jupiter-engine/src/main/java/module-info.java +++ b/junit-jupiter-engine/src/main/java/module-info.java @@ -20,7 +20,7 @@ module org.junit.jupiter.engine { requires static org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires org.junit.jupiter.api; requires org.junit.platform.commons; diff --git a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts index 810408bfd4c4..3ccd1df2b2aa 100644 --- a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts +++ b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts @@ -12,7 +12,7 @@ dependencies { api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-jupiter-migrationsupport/src/main/java/module-info.java b/junit-jupiter-migrationsupport/src/main/java/module-info.java index e89e043930e3..f2e222996666 100644 --- a/junit-jupiter-migrationsupport/src/main/java/module-info.java +++ b/junit-jupiter-migrationsupport/src/main/java/module-info.java @@ -16,7 +16,7 @@ module org.junit.jupiter.migrationsupport { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires transitive junit; // 4 requires transitive org.junit.jupiter.api; diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 6cb6fde4aa2a..61649b38872a 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -17,7 +17,7 @@ dependencies { api(projects.junitJupiterApi) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) shadowed(libs.fastcsv) diff --git a/junit-jupiter-params/src/main/java/module-info.java b/junit-jupiter-params/src/main/java/module-info.java index f7ef36172179..a272618d83f1 100644 --- a/junit-jupiter-params/src/main/java/module-info.java +++ b/junit-jupiter-params/src/main/java/module-info.java @@ -16,7 +16,7 @@ module org.junit.jupiter.params { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires transitive org.junit.jupiter.api; requires transitive org.junit.platform.commons; diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index 0dd0f1b66209..8311855edefc 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -16,7 +16,7 @@ dependencies { api(projects.junitPlatformReporting) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) shadowed(libs.picocli) diff --git a/junit-platform-console/src/main/java/module-info.java b/junit-platform-console/src/main/java/module-info.java index 0fe1a03f0cf7..336c201f8252 100644 --- a/junit-platform-console/src/main/java/module-info.java +++ b/junit-platform-console/src/main/java/module-info.java @@ -17,7 +17,7 @@ module org.junit.platform.console { requires static org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires org.junit.platform.commons; requires org.junit.platform.engine; diff --git a/junit-platform-engine/src/main/java/module-info.java b/junit-platform-engine/src/main/java/module-info.java index d23c9872dad9..c81836121c9c 100644 --- a/junit-platform-engine/src/main/java/module-info.java +++ b/junit-platform-engine/src/main/java/module-info.java @@ -19,7 +19,7 @@ module org.junit.platform.engine { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires transitive org.junit.platform.commons; requires transitive org.opentest4j; diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts index dd6a9ff2a991..dcd79e840206 100644 --- a/junit-platform-launcher/junit-platform-launcher.gradle.kts +++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts @@ -11,7 +11,7 @@ dependencies { api(projects.junitPlatformEngine) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(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 d449031ae040..59d03062d287 100644 --- a/junit-platform-launcher/src/main/java/module-info.java +++ b/junit-platform-launcher/src/main/java/module-info.java @@ -24,7 +24,7 @@ module org.junit.platform.launcher { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires static jdk.jfr; requires transitive java.logging; diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts index 5bc26173b981..51357041b8b5 100644 --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -16,7 +16,7 @@ dependencies { implementation(libs.openTestReporting.tooling.spi) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) 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 11202c5829c3..5b84f82b2d99 100644 --- a/junit-platform-reporting/src/main/java/module-info.java +++ b/junit-platform-reporting/src/main/java/module-info.java @@ -16,7 +16,7 @@ module org.junit.platform.reporting { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires java.xml; requires org.junit.platform.commons; 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 a5f73150fae6..7ff67eeaef9f 100644 --- a/junit-platform-suite-api/junit-platform-suite-api.gradle.kts +++ b/junit-platform-suite-api/junit-platform-suite-api.gradle.kts @@ -10,7 +10,7 @@ dependencies { api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(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 690db0fe2d21..9886bfb3912c 100644 --- a/junit-platform-suite-api/src/main/java/module-info.java +++ b/junit-platform-suite-api/src/main/java/module-info.java @@ -16,7 +16,7 @@ module org.junit.platform.suite.api { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires transitive org.junit.platform.commons; 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 e44887b6cdcb..fab263c8f467 100644 --- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -11,7 +11,7 @@ dependencies { api(projects.junitPlatformSuiteApi) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) implementation(projects.junitPlatformLauncher) 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 0f321578dc39..ec8f69d6af62 100644 --- a/junit-platform-suite-engine/src/main/java/module-info.java +++ b/junit-platform-suite-engine/src/main/java/module-info.java @@ -18,7 +18,7 @@ module org.junit.platform.suite.engine { requires static org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires org.junit.platform.suite.api; requires org.junit.platform.commons; diff --git a/junit-platform-testkit/junit-platform-testkit.gradle.kts b/junit-platform-testkit/junit-platform-testkit.gradle.kts index 7cb29ac6f662..7da7b65bb256 100644 --- a/junit-platform-testkit/junit-platform-testkit.gradle.kts +++ b/junit-platform-testkit/junit-platform-testkit.gradle.kts @@ -12,7 +12,7 @@ dependencies { api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(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 cbfbd2a41393..afe0b4196385 100644 --- a/junit-platform-testkit/src/main/java/module-info.java +++ b/junit-platform-testkit/src/main/java/module-info.java @@ -17,7 +17,7 @@ module org.junit.platform.testkit { requires static transitive org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires transitive org.assertj.core; requires org.junit.platform.commons; diff --git a/junit-vintage-engine/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts index 73fb5b3ffa45..493ac94e0605 100644 --- a/junit-vintage-engine/junit-vintage-engine.gradle.kts +++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts @@ -15,7 +15,7 @@ dependencies { api(libs.junit4) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) testFixturesApi(platform(libs.groovy2.bom)) testFixturesApi(libs.spock1) diff --git a/junit-vintage-engine/src/main/java/module-info.java b/junit-vintage-engine/src/main/java/module-info.java index c6e3e02fe161..ea75899aed1a 100644 --- a/junit-vintage-engine/src/main/java/module-info.java +++ b/junit-vintage-engine/src/main/java/module-info.java @@ -20,7 +20,7 @@ module org.junit.vintage.engine { requires static org.apiguardian.api; - requires static org.jspecify; + requires static transitive org.jspecify; requires junit; // 4 requires org.junit.platform.engine; 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 7fa1fa9cf145..e0dc613a1e0f 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,7 +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.jspecify static transitive 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 65a155299cf8..78c00af2df1b 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,7 +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.jspecify static transitive 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 933d05fecf74..8323d1f8c8bc 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,6 +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.jspecify static transitive 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 95e8e08ab7f7..5154e4eaace7 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,7 +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.jspecify static transitive 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-console.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-console.expected.txt index 01086247b6b6..fe6d27dbd3fe 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,7 +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.jspecify static transitive 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 cc88b0d84c7e..4997294a0477 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,7 +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.jspecify static transitive 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-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index 7c7beb927250..3bf55cd46f61 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 @@ -7,7 +7,7 @@ requires java.base mandated requires java.logging transitive requires jdk.jfr static requires org.apiguardian.api static transitive -requires org.jspecify static +requires org.jspecify static transitive 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 c9f06e55f62b..13a31d514b37 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,7 +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.jspecify static transitive 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 5b5b0b36a9b3..af2ed68b0fe1 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,5 +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.jspecify static transitive requires org.junit.platform.commons 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 fb75824c826c..3e285c1c978a 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,7 +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.jspecify static transitive 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 008e2f1caf0a..b0a938fc14f4 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,7 +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.jspecify static transitive 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 79a358e40a59..8d3bf8d6650f 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,7 +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.jspecify static transitive requires org.junit.platform.engine provides org.junit.platform.engine.TestEngine with org.junit.vintage.engine.VintageTestEngine contains org.junit.vintage.engine From 94ae7ef10e52a55997aef8dcf11070f2a1b29ed7 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:03:08 +0200 Subject: [PATCH 022/162] Polish release notes --- .../release-notes/release-notes-5.13.2.adoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc index 9393b85128b8..92d774120e0b 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc @@ -18,13 +18,13 @@ repository on GitHub. * If Git information is included in the Open Test Reporting XML format (see below), any credentials that may be configured as part the `remote.origin.url` setting in Git were - written to the `originUrl` attribute of `` elements. For example, when - cloning a GitHub repository using a URL like - `https://username:password@github.com/organization/repository.git` both username and - password were included in the XML report. Since this report may be shared, published, or - archived (for example, on a CI server) while including this information, this was - reported as a potential security vulnerability (CVE-2025-53103). Any credentials are now - being replaced with `\***` before writing them to the XML report. + previously written to the `originUrl` attribute of `` elements. For + example, when cloning a GitHub repository using a URL like + `https://username:password@github.com/organization/repository.git`, both username and + password were included in the XML report. Since a report which includes this information + may be shared, published, or archived (for example, on a CI server), this was reported + as a potential security vulnerability (CVE-2025-53103). Any credentials are now replaced + with `\***` before writing them to the XML report. [[release-notes-5.13.2-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes From 343e25338e2d0fd1387aef637a6ae3607c7c7c01 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Tue, 1 Jul 2025 15:52:32 +0200 Subject: [PATCH 023/162] Create .jitpack.yml (#4706) Re-create `.jitpack.yml` and merge Gradle commands and add toolchain info. --------- Co-authored-by: Marc Philipp --- .jitpack.yml | 6 ++++++ .../junitbuild.publishing-conventions.gradle.kts | 15 --------------- 2 files changed, 6 insertions(+), 15 deletions(-) create mode 100644 .jitpack.yml diff --git a/.jitpack.yml b/.jitpack.yml new file mode 100644 index 000000000000..4b239cdf1e36 --- /dev/null +++ b/.jitpack.yml @@ -0,0 +1,6 @@ +before_install: + - sdk update + - sdk install java 24-open + - sdk use java 24-open +install: + - ./gradlew --show-version javaToolchains publishToMavenLocal diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts index 30fedc5c2fc2..0783f037b2be 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts @@ -18,21 +18,6 @@ group = when (project) { else -> "org.junit" } -// ensure project is built successfully before publishing it -tasks.withType().configureEach { - dependsOn(provider { - val tempRepoName: String by rootProject - if (repository.name != tempRepoName) { - listOf(tasks.build) - } else { - emptyList() - } - }) -} -tasks.withType().configureEach { - dependsOn(tasks.build) -} - val signArtifacts = buildParameters.publishing.signArtifacts.getOrElse(!(project.version.isSnapshot() || buildParameters.ci)) signing { From 942a2f8793067ea0b14b56bf6939ddfa82cd2eab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:53:24 +0000 Subject: [PATCH 024/162] Update plugin shadow to v8.3.8 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1f20769f287e..ab79a49cdf96 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -107,5 +107,5 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } openrewrite = { id = "org.openrewrite.rewrite", version = "7.9.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } -shadow = { id = "com.gradleup.shadow", version = "8.3.7" } +shadow = { id = "com.gradleup.shadow", version = "8.3.8" } spotless = { id = "com.diffplug.spotless", version = "7.0.4" } From 9568c8bafe611fceabf2e37606098beb730ed9a8 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:42:59 +0200 Subject: [PATCH 025/162] Consistently use ParameterizedInvocationConstants in ParameterizedTest --- .../org/junit/jupiter/params/ParameterizedTest.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java index 129bde582290..9707229f6674 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java @@ -161,7 +161,7 @@ */ @API(status = DEPRECATED, since = "5.13") @Deprecated - String INDEX_PLACEHOLDER = "{index}"; + String INDEX_PLACEHOLDER = ParameterizedInvocationConstants.INDEX_PLACEHOLDER; /** * See {@link ParameterizedInvocationConstants#ARGUMENTS_PLACEHOLDER}. @@ -173,7 +173,7 @@ */ @API(status = DEPRECATED, since = "5.13") @Deprecated - String ARGUMENTS_PLACEHOLDER = "{arguments}"; + String ARGUMENTS_PLACEHOLDER = ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; /** * See @@ -188,7 +188,7 @@ */ @API(status = DEPRECATED, since = "5.13") @Deprecated - String ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentsWithNames}"; + String ARGUMENTS_WITH_NAMES_PLACEHOLDER = ParameterizedInvocationConstants.ARGUMENTS_WITH_NAMES_PLACEHOLDER; /** * See @@ -204,7 +204,7 @@ */ @API(status = DEPRECATED, since = "5.13") @Deprecated - String ARGUMENT_SET_NAME_PLACEHOLDER = "{argumentSetName}"; + String ARGUMENT_SET_NAME_PLACEHOLDER = ParameterizedInvocationConstants.ARGUMENT_SET_NAME_PLACEHOLDER; /** * See @@ -222,7 +222,8 @@ */ @API(status = DEPRECATED, since = "5.13") @Deprecated - String ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER = "{argumentSetNameOrArgumentsWithNames}"; + String ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER = // + ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; /** * See From 6b7f17dc8c5ce8486ad33b6c91d591c62714df13 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:45:07 +0200 Subject: [PATCH 026/162] Improve display names in tests See #4698 See #4699 --- .../jupiter/api/condition/ConditionEvaluationResultTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java index d4949cbef155..671b4bff4240 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -156,7 +156,7 @@ void disabledWithValidDefaultReasonAndCustomReason() { } @Retention(RetentionPolicy.RUNTIME) - @ParameterizedTest + @ParameterizedTest(name = "[{index}] reason=\"{0}\"") @NullSource @ValueSource(strings = { "", " ", " ", "\t", "\n" }) @interface EmptyReasonsTest { From 6beec692f6cb2a5277c0a633c7e171430fed4099 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:29:43 +0200 Subject: [PATCH 027/162] Simplify test --- .../jupiter/params/ParameterizedClassIntegrationTests.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 7f5ea985a705..1eac5e4d9d01 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 @@ -727,10 +727,9 @@ void failsForLifecycleMethodWithInvalidParameters() { var expectedMessage = """ 2 configuration errors: - - parameter 'value' with index 0 is incompatible with the parameter declared on the parameterized class: expected type 'int' but found 'long' - - parameter 'anotherValue' with index 1 must not be annotated with @ConvertWith - """; - expectedMessage = expectedMessage.strip() // + - parameter 'value' with index 0 is incompatible with the parameter declared on the parameterized class: \ + expected type 'int' but found 'long' + - parameter 'anotherValue' with index 1 must not be annotated with @ConvertWith"""// .replace("\n", System.lineSeparator()); // use platform-specific line separators var failedResult = getFirstTestExecutionResult(results.containerEvents().failed()); From a0879c83bb6567195dde1f295910f2839bb8487f Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:42:42 +0200 Subject: [PATCH 028/162] Fix multiple issues with ConditionEvaluationResult reason values Prior to this commit, there were a few long-standing issues with ConditionEvaluationResult, as outlined in #4715. This commit addresses those issues as follows. - When a "custom" reason is supplied along with a null value for the default reason to ConditionEvaluationResult.disabled(String, String), the generated reason is now "custom" instead of "null ==> custom". - A blank reason (such as an empty string or a string containing only whitespace) is now treated the same as a null reason, resulting in an "empty" reason. - The Javadoc for all factory methods in ConditionEvaluationResult now explicitly states that null or blank values are supported for reasons and that such values will result in an "empty" reason (i.e. an empty Optional). See: #4698 Closes: #4715 --- .../release-notes/release-notes-5.13.3.adoc | 12 ++- .../extension/ConditionEvaluationResult.java | 29 +++++-- .../ConditionEvaluationResultTests.java | 87 +++++-------------- 3 files changed, 57 insertions(+), 71 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index faadcc279e0b..a386778d50e0 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -39,6 +39,10 @@ repository on GitHub. with `@Nested`. * Stop reporting discovery issues for `DefaultImpls` classes generated by the Kotlin compiler for interfaces with non-abstract test methods. +* When a `customReason` is supplied along with a `null` value for the default `reason` to + `ConditionEvaluationResult.disabled(String, String)`, the resulting reason is now + `"my custom reason"` instead of + `"null ++==>++ my custom reason"`. [[release-notes-5.13.3-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes @@ -48,7 +52,13 @@ repository on GitHub. [[release-notes-5.13.3-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* A _blank_ reason supplied to a `ConditionEvaluationResult` factory method is now treated + the same as a `null` reason, resulting in an _empty_ `Optional` returned from + `ConditionEvaluationResult.getReason()`. +* The Javadoc for factory methods in `ConditionEvaluationResult` now explicitly states + that both `null` and _blank_ values are supported for reason strings and that such + values will result in an _empty_ `Optional` returned from + `ConditionEvaluationResult.getReason()`. [[release-notes-5.13.3-junit-vintage]] diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 5b683e43f32b..9f923c0db76e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -29,8 +29,11 @@ public class ConditionEvaluationResult { /** * Factory for creating enabled results. * - * @param reason the reason why the container or test should be enabled + * @param reason the reason why the container or test should be enabled; may + * be {@code null} or blank if the reason is unknown * @return an enabled {@code ConditionEvaluationResult} with the given reason + * or an empty reason if the reason is unknown + * @see StringUtils#isBlank() */ public static ConditionEvaluationResult enabled(String reason) { return new ConditionEvaluationResult(true, reason); @@ -39,8 +42,11 @@ public static ConditionEvaluationResult enabled(String reason) { /** * Factory for creating disabled results. * - * @param reason the reason why the container or test should be disabled + * @param reason the reason why the container or test should be disabled; may + * be {@code null} or blank if the reason is unknown * @return a disabled {@code ConditionEvaluationResult} with the given reason + * or an empty reason if the reason is unknown + * @see StringUtils#isBlank() */ public static ConditionEvaluationResult disabled(String reason) { return new ConditionEvaluationResult(false, reason); @@ -50,13 +56,23 @@ public static ConditionEvaluationResult disabled(String reason) { * Factory for creating disabled results with custom reasons * added by the user. * - * @param reason the default reason why the container or test should be disabled - * @param customReason the custom reason why the container or test should be disabled - * @return a disabled {@code ConditionEvaluationResult} with the given reasons + *

If non-blank default and custom reasons are provided, they will be + * concatenated using the format: "reason ==> customReason". + * + * @param reason the default reason why the container or test should be disabled; + * may be {@code null} or blank if the default reason is unknown + * @param customReason the custom reason why the container or test should be + * disabled; may be {@code null} or blank if the custom reason is unknown + * @return a disabled {@code ConditionEvaluationResult} with the given reason(s) + * or an empty reason if the reasons are unknown * @since 5.7 + * @see StringUtils#isBlank() */ @API(status = STABLE, since = "5.7") public static ConditionEvaluationResult disabled(String reason, String customReason) { + if (StringUtils.isBlank(reason)) { + return disabled(customReason); + } if (StringUtils.isBlank(customReason)) { return disabled(reason); } @@ -67,9 +83,10 @@ public static ConditionEvaluationResult disabled(String reason, String customRea private final Optional reason; + @SuppressWarnings("NullAway") // StringUtils.isBlank() does not yet have a nullability @Contract private ConditionEvaluationResult(boolean enabled, String reason) { this.enabled = enabled; - this.reason = Optional.ofNullable(reason); + this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); } /** diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java index 671b4bff4240..8e35aec1decf 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -39,24 +39,15 @@ void enabledWithReason() { .isEqualTo("ConditionEvaluationResult [enabled = true, reason = 'reason']"); } - @EmptyReasonsTest - void enabledWithInvalidReason(@Nullable String reason) { + @BlankReasonsTest + void enabledWithBlankReason(@Nullable String reason) { @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.enabled(reason); assertThat(result.isDisabled()).isFalse(); - - if (reason == null) { - assertThat(result.getReason()).isEmpty(); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = true, reason = '']"); - } - // TODO Remove else-block once issues are addressed. - else { - assertThat(result.getReason()).contains(reason); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = true, reason = '%s']", reason); - } + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = true, reason = '']"); } @Test @@ -69,28 +60,19 @@ void disabledWithDefaultReason() { .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); } - @EmptyReasonsTest - void disabledWithInvalidDefaultReason(@Nullable String reason) { + @BlankReasonsTest + void disabledWithBlankDefaultReason(@Nullable String reason) { @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled(reason); assertThat(result.isDisabled()).isTrue(); - - if (reason == null) { - assertThat(result.getReason()).isEmpty(); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); - } - // TODO Remove else-block once issues are addressed. - else { - assertThat(result.getReason()).contains(reason); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '%s']", reason); - } + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); } - @EmptyReasonsTest - void disabledWithValidDefaultReasonAndInvalidCustomReason(@Nullable String customReason) { + @BlankReasonsTest + void disabledWithDefaultReasonAndBlankCustomReason(@Nullable String customReason) { @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled("default", customReason); @@ -100,53 +82,30 @@ void disabledWithValidDefaultReasonAndInvalidCustomReason(@Nullable String custo .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); } - @EmptyReasonsTest - void disabledWithInvalidDefaultReasonAndValidCustomReason(@Nullable String reason) { + @BlankReasonsTest + void disabledWithBlankDefaultReasonAndCustomReason(@Nullable String reason) { @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled(reason, "custom"); assertThat(result.isDisabled()).isTrue(); - - // TODO Convert to single assertion once issues are addressed. - // The following should hold for all null/blank default reasons. - // assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'custom']"); - - if (reason == null) { - assertThat(result.getReason()).contains("null ==> custom"); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'null ==> custom']"); - } - else { - var generatedReason = reason + " ==> custom"; - assertThat(result.getReason()).contains(generatedReason); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '%s']", generatedReason); - } + assertThat(result.getReason()).contains("custom"); + assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'custom']"); } - @EmptyReasonsTest - void disabledWithInvalidDefaultReasonAndInvalidCustomReason(@Nullable String reason) { + @BlankReasonsTest + void disabledWithBlankDefaultReasonAndBlankCustomReason(@Nullable String reason) { // We intentionally use the reason as both the default and custom reason. @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled(reason, reason); assertThat(result.isDisabled()).isTrue(); - - if (reason == null) { - assertThat(result.getReason()).isEmpty(); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); - } - // TODO Remove else-block once issues are addressed. - else { - assertThat(result.getReason()).contains(reason); - assertThat(result).asString()// - .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '%s']", reason); - } + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); } @Test - void disabledWithValidDefaultReasonAndCustomReason() { + void disabledWithDefaultReasonAndCustomReason() { var result = ConditionEvaluationResult.disabled("default", "custom"); assertThat(result.isDisabled()).isTrue(); @@ -159,7 +118,7 @@ void disabledWithValidDefaultReasonAndCustomReason() { @ParameterizedTest(name = "[{index}] reason=\"{0}\"") @NullSource @ValueSource(strings = { "", " ", " ", "\t", "\n" }) - @interface EmptyReasonsTest { + @interface BlankReasonsTest { } } From fd6b40ba08aff580bd450ab595a9bfb6b185832a Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:44:39 +0200 Subject: [PATCH 029/162] Strip reasons when concatenating them in ConditionEvaluationResult Prior to this commit, invoking: ConditionEvaluationResult.disabled(" x ", " y ") Resulted in: " x ==> y " With this change, the result is now: "x ==> y" See: #4698 Closes: #4715 --- .../api/extension/ConditionEvaluationResult.java | 2 +- .../api/condition/ConditionEvaluationResultTests.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 9f923c0db76e..82c7ac5613e6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -76,7 +76,7 @@ public static ConditionEvaluationResult disabled(String reason, String customRea if (StringUtils.isBlank(customReason)) { return disabled(reason); } - return disabled("%s ==> %s".formatted(reason, customReason)); + return disabled("%s ==> %s".formatted(reason.strip(), customReason.strip())); } private final boolean enabled; diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java index 8e35aec1decf..bb00488105b6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -106,7 +106,16 @@ void disabledWithBlankDefaultReasonAndBlankCustomReason(@Nullable String reason) @Test void disabledWithDefaultReasonAndCustomReason() { - var result = ConditionEvaluationResult.disabled("default", "custom"); + disabledWithDefaultReasonAndCustomReason("default", "custom"); + } + + @Test + void disabledWithDefaultReasonAndCustomReasonWithLeadingAndTrailingWhitespace() { + disabledWithDefaultReasonAndCustomReason(" default ", " custom "); + } + + private static void disabledWithDefaultReasonAndCustomReason(String defaultReason, String customReason) { + var result = ConditionEvaluationResult.disabled(defaultReason, customReason); assertThat(result.isDisabled()).isTrue(); assertThat(result.getReason()).contains("default ==> custom"); From 61894ae6a1058e143a51950bb0beaa211ce0b247 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:07:10 +0200 Subject: [PATCH 030/162] Fix Javadoc references See: #4715 --- .../jupiter/api/extension/ConditionEvaluationResult.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 82c7ac5613e6..3ec78cd392d5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -33,7 +33,7 @@ public class ConditionEvaluationResult { * be {@code null} or blank if the reason is unknown * @return an enabled {@code ConditionEvaluationResult} with the given reason * or an empty reason if the reason is unknown - * @see StringUtils#isBlank() + * @see StringUtils#isBlank(String) */ public static ConditionEvaluationResult enabled(String reason) { return new ConditionEvaluationResult(true, reason); @@ -46,7 +46,7 @@ public static ConditionEvaluationResult enabled(String reason) { * be {@code null} or blank if the reason is unknown * @return a disabled {@code ConditionEvaluationResult} with the given reason * or an empty reason if the reason is unknown - * @see StringUtils#isBlank() + * @see StringUtils#isBlank(String) */ public static ConditionEvaluationResult disabled(String reason) { return new ConditionEvaluationResult(false, reason); @@ -66,7 +66,7 @@ public static ConditionEvaluationResult disabled(String reason) { * @return a disabled {@code ConditionEvaluationResult} with the given reason(s) * or an empty reason if the reasons are unknown * @since 5.7 - * @see StringUtils#isBlank() + * @see StringUtils#isBlank(String) */ @API(status = STABLE, since = "5.7") public static ConditionEvaluationResult disabled(String reason, String customReason) { From 3adeb163c21c3045d4c2b0101bde8ed4fd008978 Mon Sep 17 00:00:00 2001 From: Eric Giese Date: Thu, 3 Jul 2025 07:53:25 +0200 Subject: [PATCH 031/162] Add `TestTask::getTestDescriptor` method (#4717) Since a `TestTask` always corresponds to a specific test node, making its descriptor available for further analysis in custom test engines may be useful. Resolves #4711. --------- Co-authored-by: Marc Philipp --- .../HierarchicalTestExecutorService.java | 13 +++++++++++++ .../engine/support/hierarchical/NodeTestTask.java | 5 +++++ 2 files changed, 18 insertions(+) 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 1fed0663e519..23be08c4ed82 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 @@ -10,6 +10,7 @@ package org.junit.platform.engine.support.hierarchical; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; @@ -18,6 +19,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.engine.ExecutionRequest; +import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; /** @@ -95,6 +97,17 @@ interface TestTask { */ ResourceLock getResourceLock(); + /** + * Get the {@linkplain TestDescriptor test descriptor} of this task. + * + * @throws UnsupportedOperationException if not supported for this TestTask implementation + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + default TestDescriptor getTestDescriptor() { + throw new UnsupportedOperationException(); + } + /** * Execute this task. */ 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 5249236fd729..6fb309dbab65 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 @@ -86,6 +86,11 @@ public ExecutionMode getExecutionMode() { .orElseGet(node::getExecutionMode); } + @Override + public TestDescriptor getTestDescriptor() { + return testDescriptor; + } + @Override public String toString() { return "NodeTestTask [" + testDescriptor + "]"; From 5b3448d67b9f397e478098286fd56faca9a86e63 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 3 Jul 2025 07:21:27 +0200 Subject: [PATCH 032/162] Add #4711 to release notes --- .../docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index fdcdfefe2345..412165b0c8da 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -27,7 +27,8 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Introduce `TestTask.getTestDescriptor()` method for use in + `HierarchicalTestExecutorService` implementations. [[release-notes-6.0.0-M2-junit-jupiter]] From 9549b59293f2c616490ea883bcd51789d7a008be Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 3 Jul 2025 10:45:58 +0000 Subject: [PATCH 033/162] Improve message of discovery issues for ineffective `@Order` annotations (#4718) Resolves #4713. --- .../asciidoc/release-notes/release-notes-5.13.3.adoc | 1 + .../jupiter/engine/discovery/ClassOrderingVisitor.java | 10 ++++++---- .../engine/discovery/MethodOrderingVisitor.java | 10 ++++++---- .../jupiter/engine/extension/OrderedClassTests.java | 4 +++- .../jupiter/engine/extension/OrderedMethodTests.java | 4 +++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index a386778d50e0..10dc0f391461 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -59,6 +59,7 @@ repository on GitHub. that both `null` and _blank_ values are supported for reason strings and that such values will result in an _empty_ `Optional` returned from `ConditionEvaluationResult.getReason()`. +* Improve message of discovery issues reported for ineffective `@Order` annotations. [[release-notes-5.13.3-junit-vintage]] diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java index 2d6502f6c898..b45250e06fbe 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java @@ -28,7 +28,6 @@ import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; @@ -49,10 +48,13 @@ class ClassOrderingVisitor extends AbstractOrderingVisitor { this.globalOrderer = createGlobalOrderer(configuration); this.noOrderAnnotation = issueReporter.createReportingCondition( testDescriptor -> !isAnnotated(testDescriptor.getTestClass(), Order.class), testDescriptor -> { - String message = "Ineffective @Order annotation on class '%s'. It will not be applied because ClassOrderer.OrderAnnotation is not in use.".formatted( - testDescriptor.getTestClass().getName()); + String message = """ + Ineffective @Order annotation on class '%s'. \ + It will not be applied because ClassOrderer.OrderAnnotation is not in use. \ + Note that the annotation may be either directly present or meta-present on the class."""// + .formatted(testDescriptor.getTestClass().getName()); return DiscoveryIssue.builder(Severity.INFO, message) // - .source(ClassSource.from(testDescriptor.getTestClass())) // + .source(testDescriptor.getSource()) // .build(); }); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java index de4646820d37..6d5e6e339ab9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java @@ -30,7 +30,6 @@ import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter; import org.junit.platform.engine.support.discovery.DiscoveryIssueReporter.Condition; @@ -50,10 +49,13 @@ class MethodOrderingVisitor extends AbstractOrderingVisitor { this.configuration = configuration; this.noOrderAnnotation = issueReporter.createReportingCondition( testDescriptor -> !isAnnotated(testDescriptor.getTestMethod(), Order.class), testDescriptor -> { - String message = "Ineffective @Order annotation on method '%s'. It will not be applied because MethodOrderer.OrderAnnotation is not in use.".formatted( - testDescriptor.getTestMethod().toGenericString()); + String message = """ + Ineffective @Order annotation on method '%s'. \ + It will not be applied because MethodOrderer.OrderAnnotation is not in use. \ + Note that the annotation may be either directly present or meta-present on the method."""// + .formatted(testDescriptor.getTestMethod().toGenericString()); return DiscoveryIssue.builder(Severity.INFO, message) // - .source(MethodSource.from(testDescriptor.getTestMethod())) // + .source(testDescriptor.getSource()) // .build(); }); this.methodsBeforeNestedClassesOrderer = createMethodsBeforeNestedClassesOrderer(); 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 1954b93d35e0..dd76fa677ce9 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 @@ -205,7 +205,9 @@ private static void assertIneffectiveOrderAnnotationIssues(List assertThat(discoveryIssues).extracting(DiscoveryIssue::severity).containsOnly(Severity.INFO); assertThat(discoveryIssues).extracting(DiscoveryIssue::message) // .allMatch(it -> it.startsWith("Ineffective @Order annotation on class") - && it.endsWith("It will not be applied because ClassOrderer.OrderAnnotation is not in use.")); + && it.contains("It will not be applied because ClassOrderer.OrderAnnotation is not in use.") + && it.endsWith( + "Note that the annotation may be either directly present or meta-present on the class.")); assertThat(discoveryIssues).extracting(DiscoveryIssue::source).extracting(Optional::orElseThrow) // .containsExactlyInAnyOrder(ClassSource.from(A_TestCase.class), ClassSource.from(C_TestCase.class)); } 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 0d191dfadd99..b19ef8e08bbf 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 @@ -374,7 +374,9 @@ private static void assertIneffectiveOrderAnnotationIssues(List assertThat(discoveryIssues).extracting(DiscoveryIssue::severity).containsOnly(Severity.INFO); assertThat(discoveryIssues).extracting(DiscoveryIssue::message) // .allMatch(it -> it.startsWith("Ineffective @Order annotation on method") - && it.endsWith("It will not be applied because MethodOrderer.OrderAnnotation is not in use.")); + && it.contains("It will not be applied because MethodOrderer.OrderAnnotation is not in use.") + && it.endsWith( + "Note that the annotation may be either directly present or meta-present on the method.")); var testClass = WithoutTestMethodOrderTestCase.class; assertThat(discoveryIssues).extracting(DiscoveryIssue::source).extracting(Optional::orElseThrow) // .containsExactlyInAnyOrder(MethodSource.from(testClass.getDeclaredMethod("test1")), From aff675531acf69df8158c3d2f65b7c1816ab044b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:55:35 +0200 Subject: [PATCH 034/162] Fix comment --- .../junit/jupiter/api/extension/ConditionEvaluationResult.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 3ec78cd392d5..4cbf960d1ddd 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -83,7 +83,7 @@ public static ConditionEvaluationResult disabled(String reason, String customRea private final Optional reason; - @SuppressWarnings("NullAway") // StringUtils.isBlank() does not yet have a nullability @Contract + @SuppressWarnings("NullAway") // StringUtils.isNotBlank() does not yet have a nullability @Contract private ConditionEvaluationResult(boolean enabled, String reason) { this.enabled = enabled; this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); From 8c5ab7f9ad6fcc74ca576c57da9f0378f1eaa301 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:25:00 +0200 Subject: [PATCH 035/162] =?UTF-8?q?Support=20@=E2=81=A0=E2=81=A0Nullable?= =?UTF-8?q?=20reasons=20in=20ConditionEvaluationResult=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #4698 Closes #4699 --- .../asciidoc/release-notes/release-notes-6.0.0-M2.adoc | 5 +++-- .../api/extension/ConditionEvaluationResult.java | 10 ++++++---- .../api/condition/ConditionEvaluationResultTests.java | 5 ----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 412165b0c8da..291775e214ed 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -22,7 +22,7 @@ repository on GitHub. ==== Deprecations and Breaking Changes * Discontinue `junit-platform-suite-commons` which is now integrated into - `junit-platform-suite` + `junit-platform-suite`. [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements @@ -47,7 +47,8 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Reason strings supplied to `ConditionEvaluationResult` APIs are now officially declared + as `@Nullable`. [[release-notes-6.0.0-M2-junit-vintage]] diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 4cbf960d1ddd..c4de9b5bcd84 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -15,6 +15,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; @@ -35,7 +36,7 @@ public class ConditionEvaluationResult { * or an empty reason if the reason is unknown * @see StringUtils#isBlank(String) */ - public static ConditionEvaluationResult enabled(String reason) { + public static ConditionEvaluationResult enabled(@Nullable String reason) { return new ConditionEvaluationResult(true, reason); } @@ -48,7 +49,7 @@ public static ConditionEvaluationResult enabled(String reason) { * or an empty reason if the reason is unknown * @see StringUtils#isBlank(String) */ - public static ConditionEvaluationResult disabled(String reason) { + public static ConditionEvaluationResult disabled(@Nullable String reason) { return new ConditionEvaluationResult(false, reason); } @@ -69,7 +70,8 @@ public static ConditionEvaluationResult disabled(String reason) { * @see StringUtils#isBlank(String) */ @API(status = STABLE, since = "5.7") - public static ConditionEvaluationResult disabled(String reason, String customReason) { + @SuppressWarnings("NullAway") // StringUtils.isBlank() does not yet have a nullability @Contract + public static ConditionEvaluationResult disabled(@Nullable String reason, @Nullable String customReason) { if (StringUtils.isBlank(reason)) { return disabled(customReason); } @@ -84,7 +86,7 @@ public static ConditionEvaluationResult disabled(String reason, String customRea private final Optional reason; @SuppressWarnings("NullAway") // StringUtils.isNotBlank() does not yet have a nullability @Contract - private ConditionEvaluationResult(boolean enabled, String reason) { + private ConditionEvaluationResult(boolean enabled, @Nullable String reason) { this.enabled = enabled; this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java index bb00488105b6..e82e5fa6df53 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -41,7 +41,6 @@ void enabledWithReason() { @BlankReasonsTest void enabledWithBlankReason(@Nullable String reason) { - @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.enabled(reason); assertThat(result.isDisabled()).isFalse(); @@ -62,7 +61,6 @@ void disabledWithDefaultReason() { @BlankReasonsTest void disabledWithBlankDefaultReason(@Nullable String reason) { - @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled(reason); assertThat(result.isDisabled()).isTrue(); @@ -73,7 +71,6 @@ void disabledWithBlankDefaultReason(@Nullable String reason) { @BlankReasonsTest void disabledWithDefaultReasonAndBlankCustomReason(@Nullable String customReason) { - @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled("default", customReason); assertThat(result.isDisabled()).isTrue(); @@ -84,7 +81,6 @@ void disabledWithDefaultReasonAndBlankCustomReason(@Nullable String customReason @BlankReasonsTest void disabledWithBlankDefaultReasonAndCustomReason(@Nullable String reason) { - @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled(reason, "custom"); assertThat(result.isDisabled()).isTrue(); @@ -95,7 +91,6 @@ void disabledWithBlankDefaultReasonAndCustomReason(@Nullable String reason) { @BlankReasonsTest void disabledWithBlankDefaultReasonAndBlankCustomReason(@Nullable String reason) { // We intentionally use the reason as both the default and custom reason. - @SuppressWarnings("NullAway") var result = ConditionEvaluationResult.disabled(reason, reason); assertThat(result.isDisabled()).isTrue(); From 649ea9255536c8951b116d8596d66d7317a61045 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 4 Jul 2025 11:50:46 +0200 Subject: [PATCH 036/162] Manage version of openrewrite-recipe-bom --- gradle/libs.versions.toml | 1 + .../main/kotlin/junitbuild.java-library-conventions.gradle.kts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab79a49cdf96..6aa669dd0f17 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,6 +61,7 @@ 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" } +openrewrite-recipe-bom = { module = "org.openrewrite.recipe:rewrite-recipe-bom", version = "3.10.1" } 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" } 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 c26214443f8a..a505a3c5b6e8 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,4 +1,5 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import junitbuild.extensions.dependencyFromLibs import junitbuild.extensions.isSnapshot import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.Library @@ -21,7 +22,7 @@ rewrite { } dependencies { - rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:latest.release")) + rewrite(platform(dependencyFromLibs("openrewrite-recipe-bom"))) rewrite("org.openrewrite.recipe:rewrite-migrate-java") } From 890fdaef70f487e9e3b257cd057c2e653252cba5 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 4 Jul 2025 14:10:24 +0200 Subject: [PATCH 037/162] Release 5.13.3 --- README.md | 2 +- .../release-notes/release-notes-5.13.3.adoc | 37 ++----------------- 2 files changed, 4 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index f1be8cc97ecc..a78344e3e773 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This repository is the home of JUnit Platform, Jupiter, and Vintage. ## Latest Releases -- General Availability (GA): [JUnit 5.13.2](https://github.com/junit-team/junit-framework/releases/tag/r5.13.2) (June 24, 2025) +- General Availability (GA): [JUnit 5.13.3](https://github.com/junit-team/junit-framework/releases/tag/r5.13.3) (July 4, 2025) - Preview (Milestone/Release Candidate): [JUnit 6.0.0-M1](https://github.com/junit-team/junit-framework/releases/tag/r6.0.0-M1) (June 27, 2025) ## Documentation diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index 10dc0f391461..670bf3c42edf 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -1,7 +1,7 @@ [[release-notes-5.13.3]] == 5.13.3 -*Date of Release:* ❓ +*Date of Release:* July 4, 2025 *Scope:* Bug fixes and enhancements since 5.13.2 @@ -13,20 +13,7 @@ repository on GitHub. [[release-notes-5.13.3-junit-platform]] === JUnit Platform -[[release-notes-5.13.3-junit-platform-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.13.3-junit-platform-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.13.3-junit-platform-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ +No changes. [[release-notes-5.13.3-junit-jupiter]] @@ -44,11 +31,6 @@ repository on GitHub. `"my custom reason"` instead of `"null ++==>++ my custom reason"`. -[[release-notes-5.13.3-junit-jupiter-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - [[release-notes-5.13.3-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements @@ -65,17 +47,4 @@ repository on GitHub. [[release-notes-5.13.3-junit-vintage]] === JUnit Vintage -[[release-notes-5.13.3-junit-vintage-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.13.3-junit-vintage-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.13.3-junit-vintage-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ +No changes. From 927ab2a78a32a8b20807c42a526ec7332da09d9d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 4 Jul 2025 14:40:16 +0200 Subject: [PATCH 038/162] Document #4686 in release notes --- .../src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index 670bf3c42edf..2a2fc18ef130 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -22,6 +22,8 @@ No changes. [[release-notes-5.13.3-junit-jupiter-bug-fixes]] ==== Bug Fixes +* Fix regression that caused top-level and static member classes annotated with `@Nested` + to no longer be executed because they caused a discovery issue to be reported. * Stop reporting discovery issues for composed annotation classes that are meta-annotated with `@Nested`. * Stop reporting discovery issues for `DefaultImpls` classes generated by the Kotlin From 63e43cb186aa769811220b9bdd96ac4023239c2f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 4 Jul 2025 15:11:41 +0200 Subject: [PATCH 039/162] Document #4689 in release notes --- .../asciidoc/release-notes/release-notes-5.13.3.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc index 2a2fc18ef130..1628c1aded6d 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.3.adoc @@ -10,6 +10,16 @@ link:{junit-framework-repo}+/milestone/100?closed=1+[5.13.3] milestone page in t repository on GitHub. +[[release-notes-5.13.3-overall-improvements]] +=== Overall Changes + +[[release-notes-5.13.3-overall-new-features-and-improvements]] +==== New Features and Improvements + +* All _experimental_ APIs have been promoted to _maintained_ to indicate that they won't + be removed in any future 5.x release. + + [[release-notes-5.13.3-junit-platform]] === JUnit Platform From da4b045e64a721e601a0c737109a25483b91a02e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 22:52:03 +0000 Subject: [PATCH 040/162] Update dependency gradle to v8.14.3 (#4723) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3735f265b953..78cb6e16a49f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From f6f1a701dd945abd0ee44407cc26f08024a03039 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 7 Jul 2025 10:33:37 +0000 Subject: [PATCH 041/162] Introduce `LauncherExecutionRequest` (#4724) The new `Launcher.execute(LauncherExecutionRequest)` method paves the road for adding additional parameters to test execution without having to add additional overloads of `execute()`. For example, for #1880 we will likely need to introduce a `CancellationToken` (as drafted in #4709). Instead of having to add two new overloads, such changes will then be easy because only `LauncherExecutionRequest` will need to be changed. `LauncherExecutionRequestBuilder` provides a fluent API for constructing execution requests starting either with a `TestPlan` or a `LauncherDiscoveryRequest`. In addition, `LauncherDiscoveryRequestBuilder.forExecution()` is a convenience method to create an execution request from a discovery request. --- .../release-notes/release-notes-6.0.0-M2.adoc | 9 ++ .../java/example/UsingTheLauncherDemo.java | 15 +-- .../sharedresources/SharedResourceDemo.java | 2 +- .../console/tasks/ConsoleTestExecutor.java | 2 +- .../org/junit/platform/launcher/Launcher.java | 53 ++++++++++- .../platform/launcher/LauncherConstants.java | 3 +- .../launcher/LauncherDiscoveryRequest.java | 2 + .../launcher/LauncherExecutionRequest.java | 64 +++++++++++++ .../launcher/LauncherInterceptor.java | 5 +- .../launcher/TestExecutionListener.java | 3 +- .../launcher/core/DefaultLauncher.java | 27 +++--- .../core/DefaultLauncherExecutionRequest.java | 53 +++++++++++ .../launcher/core/DefaultLauncherSession.java | 8 ++ .../launcher/core/DelegatingLauncher.java | 7 ++ .../core/EngineExecutionOrchestrator.java | 7 +- .../launcher/core/InterceptingLauncher.java | 9 ++ .../core/LauncherDiscoveryRequestBuilder.java | 14 +++ .../core/LauncherExecutionRequestBuilder.java | 93 +++++++++++++++++++ .../core/SessionPerRequestLauncher.java | 10 ++ .../engine/JUnit4ParameterizedTests.java | 1 + .../VintageLauncherIntegrationTests.java | 21 +++-- .../launcher/core/DefaultLauncherTests.java | 55 ++++++----- .../LauncherConfigurationParametersTests.java | 9 +- .../launcher/core/LauncherFactoryTests.java | 64 ++++++++----- .../launcher/core/LauncherSessionTests.java | 40 ++++++-- .../launcher/core/StoreSharingTests.java | 5 +- ...TestExecutionListenerIntegrationTests.java | 14 ++- ...dingExecutionListenerIntegrationTests.java | 1 + .../listeners/LoggingListenerTests.java | 7 +- ...queIdTrackingListenerIntegrationTests.java | 19 ++-- ...egacyXmlReportGeneratingListenerTests.java | 8 +- ...OpenTestReportGeneratingListenerTests.java | 4 +- 32 files changed, 518 insertions(+), 116 deletions(-) create mode 100644 junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java create mode 100644 junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java create mode 100644 junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 291775e214ed..1cc126ffe1fa 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -23,10 +23,19 @@ repository on GitHub. * Discontinue `junit-platform-suite-commons` which is now integrated into `junit-platform-suite`. +* Deprecate `Launcher.execute(TestPlan, TestExecutionListener[])` and + `Launcher.execute(LauncherDiscoveryRequest, TestExecutionListener[])` in favor of + `Launcher.execute(LauncherExecutionRequest)` [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements +* Introduce new `Launcher.execute(LauncherExecutionRequest)` API with corresponding + `LauncherExecutionRequestBuilder` to enable the addition of parameters to test + executions without additional overloads of `execute`. +* Introduce `LauncherDiscoveryRequestBuilder.forExecution()` method as a convenience + method for constructing a `LauncherExecutionRequest` that contains a + `LauncherDiscoveryRequest`. * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. diff --git a/documentation/src/test/java/example/UsingTheLauncherDemo.java b/documentation/src/test/java/example/UsingTheLauncherDemo.java index ee5ff525c565..b2bbe4a95106 100644 --- a/documentation/src/test/java/example/UsingTheLauncherDemo.java +++ b/documentation/src/test/java/example/UsingTheLauncherDemo.java @@ -23,6 +23,7 @@ import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.PostDiscoveryFilter; @@ -30,6 +31,7 @@ import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherExecutionRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -74,7 +76,7 @@ void discovery() { void execution() { // @formatter:off // tag::execution[] - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors( selectPackage("com.example.mytests"), selectClass(MyTestClass.class) @@ -94,11 +96,11 @@ void execution() { // Register a listener of your choice launcher.registerTestExecutionListeners(listener); // Discover tests and build a test plan - TestPlan testPlan = launcher.discover(request); + TestPlan testPlan = launcher.discover(discoveryRequest); // Execute test plan - launcher.execute(testPlan); - // Alternatively, execute the request directly - launcher.execute(request); + launcher.execute(LauncherExecutionRequestBuilder.request(testPlan).build()); + // Alternatively, execute the discoveryRequest request directly + launcher.execute(LauncherExecutionRequestBuilder.request(discoveryRequest).build()); } TestExecutionSummary summary = listener.getSummary(); @@ -128,8 +130,9 @@ void launcherConfig() { .addTestExecutionListeners(new CustomTestExecutionListener()) .build(); - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + LauncherExecutionRequest request = LauncherDiscoveryRequestBuilder.request() .selectors(selectPackage("com.example.mytests")) + .forExecution() .build(); try (LauncherSession session = LauncherFactory.openSession(launcherConfig)) { diff --git a/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java b/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java index 4ca8e4a11efb..285a7f4632c2 100644 --- a/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java +++ b/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java @@ -39,7 +39,7 @@ void runBothCustomEnginesTest() { // tag::custom_line_break[] .build()); - launcher.execute(request().build()); + launcher.execute(request().forExecution().build()); assertSame(firstCustomEngine.socket, secondCustomEngine.socket); assertTrue(firstCustomEngine.socket.isClosed(), "socket should be closed"); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java index 4c001b4d806f..a030d65805e0 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java @@ -143,7 +143,7 @@ private void launchTests(Launcher launcher, Optional reportsDir) { LauncherDiscoveryRequestBuilder discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions); reportsDir.ifPresent(dir -> discoveryRequestBuilder.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, dir.toAbsolutePath().toString())); - launcher.execute(discoveryRequestBuilder.build()); + launcher.execute(discoveryRequestBuilder.forExecution().build()); } private Optional createCustomClassLoader() { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 75a5f07deabf..2e87fa874152 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -10,9 +10,12 @@ package org.junit.platform.launcher; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.launcher.core.LauncherExecutionRequestBuilder; /** * The {@code Launcher} API is the main entry point for client code that @@ -105,8 +108,16 @@ public interface Launcher { * * @param launcherDiscoveryRequest the launcher discovery request; never {@code null} * @param listeners additional test execution listeners; never {@code null} + * @deprecated Please use {@link #execute(LauncherExecutionRequest)} instead. */ - void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners); + @Deprecated + @API(status = DEPRECATED, since = "6.0") + default void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { + var executionRequest = LauncherExecutionRequestBuilder.request(launcherDiscoveryRequest) // + .listeners(listeners) // + .build(); + execute(executionRequest); + } /** * Execute the supplied {@link TestPlan} and notify @@ -123,8 +134,44 @@ public interface Launcher { * @param testPlan the test plan to execute; never {@code null} * @param listeners additional test execution listeners; never {@code null} * @since 1.4 + * @deprecated Please use {@link #execute(LauncherExecutionRequest)} instead. */ - @API(status = STABLE, since = "1.4") - void execute(TestPlan testPlan, TestExecutionListener... listeners); + @Deprecated + @API(status = DEPRECATED, since = "6.0") + default void execute(TestPlan testPlan, TestExecutionListener... listeners) { + var executionRequest = LauncherExecutionRequestBuilder.request(testPlan) // + .listeners(listeners) // + .build(); + execute(executionRequest); + } + + /** + * Execute tests according to the supplied {@link LauncherExecutionRequest} + * {@linkplain #registerTestExecutionListeners registered listeners} about + * the progress and results of the execution. + * + *

Test execution listeners supplied + * {@linkplain LauncherExecutionRequest#getAdditionalTestExecutionListeners() + * as part of the request} are registered in addition to already registered + * listeners but only for the supplied execution request. + * + * @apiNote If the execution request contains a {@link TestPlan} rather than + * a {@link LauncherDiscoveryRequest}, it must not have been executed + * previously. + * + *

If the execution request contains a {@link LauncherDiscoveryRequest}, + * calling this method will cause test discovery to be executed for all + * registered engines. If the same {@link LauncherDiscoveryRequest} was + * previously passed to {@link #discover(LauncherDiscoveryRequest)}, you + * should instead provide the resulting {@link TestPlan} as part of the + * supplied execution request to avoid the potential performance degradation + * (e.g., classpath scanning) of running test discovery twice. + * + * @param launcherExecutionRequest the launcher execution request; never + * {@code null} + * @since 6.0 + */ + @API(status = MAINTAINED, since = "6.0") + void execute(LauncherExecutionRequest launcherExecutionRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index b13d4abd13a0..b1363ada3eef 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -257,8 +257,7 @@ public class LauncherConstants { *

If not specified, the {@code Launcher} will report discovery issues * during the discovery phase if * {@link Launcher#discover(LauncherDiscoveryRequest)} is called, and during - * the execution phase if - * {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)} + * the execution phase if {@link Launcher#execute(LauncherExecutionRequest)} * is called. * * @since 1.13 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java index 058281b9b375..87e9c9cb787d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java @@ -43,6 +43,8 @@ * in the test plan. * * + *

This interface is not intended to be implemented by clients. + * * @since 1.0 * @see org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder * @see EngineDiscoveryRequest diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java new file mode 100644 index 000000000000..f58ef172b312 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.Collection; +import java.util.Optional; + +import org.apiguardian.api.API; + +/** + * {@code LauncherExecutionRequest} encapsulates a request for test execution + * passed to the {@link Launcher}. + * + *

Most importantly, a {@code LauncherExecutionRequest} contains either a + * {@link LauncherDiscoveryRequest} for on-the-fly test discovery or a + * {@link TestPlan} that has previously been discovered. + * + *

Moreover, a {@code LauncherExecutionRequest} may contain the following: + * + *

    + *
  • Additional {@linkplain TestExecutionListener Test Execution Listeners} + * that should be notified of events pertaining to this execution request.
  • + *
+ * + *

This interface is not intended to be implemented by clients. + * + * @since 6.0 + * @see org.junit.platform.launcher.core.LauncherExecutionRequestBuilder + * @see Launcher#execute(LauncherExecutionRequest) + */ +@API(status = MAINTAINED, since = "6.0") +public interface LauncherExecutionRequest { + + /** + * {@return the test plan for this execution request} + * + *

If absent, a {@link TestPlan} will be present. + */ + Optional getTestPlan(); + + /** + * {@return the discovery request for this execution request} + * + *

If absent, a {@link TestPlan} will be present. + */ + Optional getDiscoveryRequest(); + + /** + * {@return the collection of additional test execution listeners that + * should be notified about events pertaining to this execution request} + */ + Collection getAdditionalTestExecutionListeners(); + +} 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 30746a6d052f..6786ab285a33 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 @@ -32,8 +32,9 @@ * *

  • * calls to {@link Launcher#discover(LauncherDiscoveryRequest)}, - * {@link Launcher#execute(TestPlan, TestExecutionListener...)}, and - * {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)} + * {@link Launcher#execute(TestPlan, TestExecutionListener...)}, + * {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)}, + * and {@link Launcher#execute(LauncherExecutionRequest)}, *
  • * * diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java index 016de421e3c1..f764c2e7127a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java @@ -22,7 +22,8 @@ /** * Register a concrete implementation of this interface with a {@link Launcher} - * to be notified of events that occur during test execution. + * or {@link LauncherExecutionRequest} to be notified of events that occur + * during test execution. * *

    All methods in this interface have empty default implementations. * Concrete implementations may therefore override one or more of these methods 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 509467bf108a..92a72e6e9b51 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 @@ -24,6 +24,7 @@ import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -84,27 +85,23 @@ public TestPlan discover(LauncherDiscoveryRequest discoveryRequest) { } @Override - public void execute(LauncherDiscoveryRequest discoveryRequest, TestExecutionListener... listeners) { - Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); - Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); - Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - execute(InternalTestPlan.from(discover(discoveryRequest, EXECUTION)), listeners); - } - - @Override - public void execute(TestPlan testPlan, TestExecutionListener... listeners) { - Preconditions.notNull(testPlan, "TestPlan must not be null"); - Preconditions.condition(testPlan instanceof InternalTestPlan, "TestPlan was not returned by this Launcher"); - Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); - Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); - execute((InternalTestPlan) testPlan, listeners); + public void execute(LauncherExecutionRequest launcherExecutionRequest) { + var testPlan = launcherExecutionRequest.getTestPlan().map(it -> { + Preconditions.condition(it instanceof InternalTestPlan, "TestPlan was not returned by this Launcher"); + return ((InternalTestPlan) it); + }).orElseGet(() -> { + Preconditions.condition(launcherExecutionRequest.getDiscoveryRequest().isPresent(), + "Either a TestPlan or LauncherDiscoveryRequest must be present in the LauncherExecutionRequest"); + return InternalTestPlan.from(discover(launcherExecutionRequest.getDiscoveryRequest().get(), EXECUTION)); + }); + execute(testPlan, launcherExecutionRequest.getAdditionalTestExecutionListeners()); } private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, LauncherPhase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } - private void execute(InternalTestPlan internalTestPlan, TestExecutionListener[] listeners) { + private void execute(InternalTestPlan internalTestPlan, Collection listeners) { try (NamespacedHierarchicalStore requestLevelStore = createRequestLevelStore()) { executionOrchestrator.execute(internalTestPlan, requestLevelStore, listeners); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java new file mode 100644 index 000000000000..6c270afbe1bb --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import org.jspecify.annotations.Nullable; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * @since 6.0 + */ +final class DefaultLauncherExecutionRequest implements LauncherExecutionRequest { + + private final @Nullable LauncherDiscoveryRequest discoveryRequest; + private final @Nullable TestPlan testPlan; + private final List executionListeners; + + DefaultLauncherExecutionRequest(@Nullable LauncherDiscoveryRequest discoveryRequest, @Nullable TestPlan testPlan, + Collection executionListeners) { + this.discoveryRequest = discoveryRequest; + this.testPlan = testPlan; + this.executionListeners = List.copyOf(executionListeners); + } + + @Override + public Optional getDiscoveryRequest() { + return Optional.ofNullable(discoveryRequest); + } + + @Override + public Optional getTestPlan() { + return Optional.ofNullable(testPlan); + } + + @Override + public Collection getAdditionalTestExecutionListeners() { + return executionListeners; + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index b128e09b1258..b53dbdf37ce3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -22,6 +22,7 @@ import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; @@ -116,15 +117,22 @@ public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { throw new PreconditionViolationException("Launcher session has already been closed"); } + @SuppressWarnings("deprecation") @Override public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + @SuppressWarnings("deprecation") @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + + @Override + public void execute(LauncherExecutionRequest launcherExecutionRequest) { + throw new PreconditionViolationException("Launcher session has already been closed"); + } } private static LauncherInterceptor composite(List interceptors) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index 3cc6be2316f0..74a8eca5250b 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -13,6 +13,7 @@ import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -42,14 +43,20 @@ public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { return delegate.discover(launcherDiscoveryRequest); } + @SuppressWarnings("deprecation") @Override public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { delegate.execute(launcherDiscoveryRequest, listeners); } + @SuppressWarnings("deprecation") @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } + @Override + public void execute(LauncherExecutionRequest launcherExecutionRequest) { + delegate.execute(launcherExecutionRequest); + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index 0536b5134736..e4c5821be3cc 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -16,6 +16,7 @@ import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import static org.junit.platform.launcher.core.ListenerRegistry.forEngineExecutionListeners; +import java.util.Collection; import java.util.Optional; import java.util.function.Consumer; @@ -56,7 +57,7 @@ public EngineExecutionOrchestrator() { } void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore, - TestExecutionListener... listeners) { + Collection listeners) { ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters(); ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution( listeners); @@ -213,8 +214,8 @@ private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryResult disco } private ListenerRegistry buildListenerRegistryForExecution( - TestExecutionListener... listeners) { - if (listeners.length == 0) { + Collection listeners) { + if (listeners.isEmpty()) { return this.listenerRegistry; } return ListenerRegistry.copyOf(this.listenerRegistry).addAll(listeners); 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 30178e808414..c0dd888681c1 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 @@ -13,6 +13,7 @@ import org.jspecify.annotations.Nullable; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -49,4 +50,12 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { return null; }); } + + @Override + public void execute(LauncherExecutionRequest launcherExecutionRequest) { + interceptor.<@Nullable Object> intercept(() -> { + super.execute(launcherExecutionRequest); + return 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 7ca99cb6c28b..93ab9afa728e 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 @@ -10,6 +10,7 @@ package org.junit.platform.launcher.core; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; @@ -323,6 +324,19 @@ else if (filter instanceof DiscoveryFilter discoveryFilter) { } } + /** + * Builds this discovery request and returns a new builder for creating a + * {@link org.junit.platform.launcher.LauncherExecutionRequest} that is + * initialized to contain the resulting discovery request. + * + * @return a new {@link LauncherExecutionRequestBuilder} + * @since 6.0 + */ + @API(status = EXPERIMENTAL, since = "6.0") + public LauncherExecutionRequestBuilder forExecution() { + return LauncherExecutionRequestBuilder.request(build()); + } + /** * Build the {@link LauncherDiscoveryRequest} that has been configured via * this builder. diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java new file mode 100644 index 000000000000..ebf01f10dfa5 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.apiguardian.api.API.Status.MAINTAINED; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +/** + * The {@code LauncherExecutionRequestBuilder} provides a light-weight DSL for + * generating a {@link LauncherExecutionRequest}. + * + * @since 6.0 + * @see LauncherExecutionRequest + */ +@API(status = MAINTAINED, since = "6.0") +public final class LauncherExecutionRequestBuilder { + + /** + * Create a new {@code LauncherExecutionRequestBuilder} from the supplied + * {@link LauncherDiscoveryRequest}. + * + * @return a new builder + */ + public static LauncherExecutionRequestBuilder request(LauncherDiscoveryRequest discoveryRequest) { + Preconditions.notNull(discoveryRequest, "LauncherDiscoveryRequest must not be null"); + return new LauncherExecutionRequestBuilder(discoveryRequest, null); + } + + /** + * Create a new {@code LauncherExecutionRequestBuilder} from the supplied + * {@link TestPlan}. + * + * @return a new builder + */ + public static LauncherExecutionRequestBuilder request(TestPlan testPlan) { + Preconditions.notNull(testPlan, "TestPlan must not be null"); + return new LauncherExecutionRequestBuilder(null, testPlan); + } + + private final @Nullable LauncherDiscoveryRequest discoveryRequest; + private final @Nullable TestPlan testPlan; + private final Collection executionListeners = new ArrayList<>(); + + private LauncherExecutionRequestBuilder(@Nullable LauncherDiscoveryRequest discoveryRequest, + @Nullable TestPlan testPlan) { + + this.discoveryRequest = discoveryRequest; + this.testPlan = testPlan; + } + + /** + * Add all supplied execution listeners to the request. + * + * @param listeners the {@code TestExecutionListener} to add; never + * {@code null} + * @return this builder for method chaining + * @see TestExecutionListener + */ + public LauncherExecutionRequestBuilder listeners(TestExecutionListener... listeners) { + Preconditions.notNull(listeners, "TestExecutionListener array must not be null"); + Preconditions.containsNoNullElements(listeners, "individual listeners must not be null"); + Collections.addAll(this.executionListeners, listeners); + return this; + } + + /** + * Build the {@link LauncherExecutionRequest} that has been configured via + * this builder. + */ + public LauncherExecutionRequest build() { + return new DefaultLauncherExecutionRequest(discoveryRequest, testPlan, executionListeners); + } + +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index cb12eafb7282..21732f0b78c7 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -19,6 +19,7 @@ import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherInterceptor; import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.LauncherSessionListener; @@ -60,6 +61,7 @@ public TestPlan discover(LauncherDiscoveryRequest launcherDiscoveryRequest) { } } + @SuppressWarnings("deprecation") @Override public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecutionListener... listeners) { try (LauncherSession session = createSession()) { @@ -67,6 +69,7 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu } } + @SuppressWarnings("deprecation") @Override public void execute(TestPlan testPlan, TestExecutionListener... listeners) { try (LauncherSession session = createSession()) { @@ -74,6 +77,13 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } + @Override + public void execute(LauncherExecutionRequest launcherExecutionRequest) { + try (LauncherSession session = createSession()) { + session.getLauncher().execute(launcherExecutionRequest); + } + } + private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, this.launcherFactory); diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java index 99253ff128c4..37f5c115637c 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java @@ -64,6 +64,7 @@ private void executeTests(DiscoverySelector selector) { .selectors(selector) .filters(includeEngines("junit-vintage")) .enableImplicitConfigurationParameters(false) + .forExecution() .build() ); // @formatter:on diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java index 9b6e3bb4811b..4ae6d522dfc3 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.TestExecutionResult; -import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -263,26 +262,30 @@ void filtersOutAllDescendantsOfParameterizedTestCase() { private TestPlan discover(LauncherDiscoveryRequestBuilder requestBuilder) { var launcher = LauncherFactory.create(); - return launcher.discover(toRequest(requestBuilder)); + return launcher.discover(toDiscoveryRequest(requestBuilder).build()); } private Map execute(LauncherDiscoveryRequestBuilder requestBuilder) { Map results = new LinkedHashMap<>(); - var request = toRequest(requestBuilder); var launcher = LauncherFactory.create(); - launcher.execute(request, new TestExecutionListener() { + var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { results.put(testIdentifier, testExecutionResult); } - }); + }; + var executionRequest = toDiscoveryRequest(requestBuilder) // + .forExecution() // + .listeners(listener) // + .build(); + launcher.execute(executionRequest); return results; } - private LauncherDiscoveryRequest toRequest(LauncherDiscoveryRequestBuilder requestBuilder) { - return requestBuilder.filters(includeEngines(ENGINE_ID)) // - .enableImplicitConfigurationParameters(false) // - .build(); + private LauncherDiscoveryRequestBuilder toDiscoveryRequest(LauncherDiscoveryRequestBuilder requestBuilder) { + return requestBuilder // + .filters(includeEngines(ENGINE_ID)) // + .enableImplicitConfigurationParameters(false); } } 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 adf70020ca02..53ea28f3395a 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 @@ -181,7 +181,10 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId inOrder.verify(discoveryListener).launcherDiscoveryFinished(request); var listener = mock(TestExecutionListener.class); - launcher.execute(testPlan, listener); + var executionRequest = LauncherExecutionRequestBuilder.request(testPlan) // + .listeners(listener) // + .build(); + launcher.execute(executionRequest); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(engineIdentifier); @@ -216,7 +219,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); @@ -240,7 +243,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); @@ -264,7 +267,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); @@ -289,7 +292,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); @@ -316,7 +319,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); var testExecutionResult = ArgumentCaptor.forClass(TestExecutionResult.class); verify(listener).executionStarted(any()); @@ -339,7 +342,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); verify(listener).executionSkipped(any(TestIdentifier.class), eq("not today")); verify(listener, times(0)).executionStarted(any()); @@ -359,7 +362,7 @@ public void execute(ExecutionRequest request) { }; var listener = mock(TestExecutionListener.class); - createLauncher(engine).execute(request().build(), listener); + createLauncher(engine).execute(request().forExecution().listeners(listener).build()); verify(listener).executionStarted(any()); verify(listener).executionFinished(any(), eq(successful())); @@ -427,7 +430,7 @@ void withoutConfigurationParameters_LauncherPassesEmptyConfigurationParametersIn var engine = new TestEngineSpy(); var launcher = createLauncher(engine); - launcher.execute(request().build()); + launcher.execute(request().forExecution().build()); var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); assertThat(configurationParameters.get("key")).isNotPresent(); @@ -438,7 +441,7 @@ void withConfigurationParameters_LauncherPassesPopulatedConfigurationParametersI var engine = new TestEngineSpy(); var launcher = createLauncher(engine); - launcher.execute(request().configurationParameter("key", "value").build()); + launcher.execute(request().configurationParameter("key", "value").forExecution().build()); var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); assertThat(configurationParameters.get("key")).isPresent(); @@ -453,7 +456,7 @@ void withoutConfigurationParameters_LookupFallsBackToSystemProperty() { var engine = new TestEngineSpy(); var launcher = createLauncher(engine); - launcher.execute(request().build()); + launcher.execute(request().forExecution().build()); var configurationParameters = requireNonNull(engine.requestForExecution).getConfigurationParameters(); var optionalFoo = configurationParameters.get(FOO); @@ -471,7 +474,7 @@ void withAdditionalListener() { var listener = new SummaryGeneratingListener(); var launcher = createLauncher(engine); - launcher.execute(request().build(), listener); + launcher.execute(request().forExecution().listeners(listener).build()); assertThat(listener.getSummary()).isNotNull(); assertThat(listener.getSummary().getContainersFoundCount()).isEqualTo(1); @@ -553,7 +556,7 @@ public void execute(ExecutionRequest request) { var launcher = createLauncher(engine); var listener = mock(TestExecutionListener.class); - launcher.execute(request().build(), listener); + launcher.execute(request().forExecution().listeners(listener).build()); var inOrder = inOrder(listener); var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); @@ -590,10 +593,11 @@ void launcherCanExecuteTestPlanExactlyOnce() { var testPlan = launcher.discover(request().build()); verify(engine, times(1)).discover(any(), any()); - launcher.execute(testPlan); + var executionRequest = LauncherExecutionRequestBuilder.request(testPlan).build(); + launcher.execute(executionRequest); verify(engine, times(1)).execute(any()); - var e = assertThrows(PreconditionViolationException.class, () -> launcher.execute(testPlan)); + var e = assertThrows(PreconditionViolationException.class, () -> launcher.execute(executionRequest)); assertEquals("TestPlan must only be executed once", e.getMessage()); } @@ -640,7 +644,12 @@ void dryRunModeReportsEventsForAllTestsButDoesNotExecuteThem() { var launcher = createLauncher(engine); TestExecutionListener listener = mock(); - launcher.execute(request().configurationParameter(DRY_RUN_PROPERTY_NAME, "true").build(), listener); + var request = request() // + .configurationParameter(DRY_RUN_PROPERTY_NAME, "true") // + .forExecution() // + .listeners(listener) // + .build(); + launcher.execute(request); var inOrder = inOrder(listener); inOrder.verify(listener).testPlanExecutionStarted(any()); @@ -994,15 +1003,19 @@ private static ReportedData execute(TestEngine engine, UnaryOperator result = new AtomicReference<>(); - launcher.execute(request, new TestExecutionListener() { + var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { if (testIdentifier.getParentId().isEmpty()) { result.set(testExecutionResult); } } - }); + }; + + var request = request() // + .configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, "false") // + .forExecution() // + .listeners(listener) // + .build(); + + var launcher = LauncherFactory.create(config); + launcher.execute(request); assertThat(requireNonNull(result.get()).getThrowable().orElseThrow()) // .hasRootCauseMessage("from execution") // @@ -345,16 +351,22 @@ void extensionCanReadValueFromSessionStoreAndReadByLauncherSessionListenerOnOpen .build(); try (LauncherSession session = LauncherFactory.openSession(config)) { - var launcher = session.getLauncher(); - var request = request().selectors(selectClass(SessionTrackingTestCase.class)).build(); AtomicReference errorRef = new AtomicReference<>(); - launcher.execute(request, new TestExecutionListener() { + var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { testExecutionResult.getThrowable().ifPresent(errorRef::set); } - }); + }; + + var request = request() // + .selectors(selectClass(SessionTrackingTestCase.class)) // + .forExecution() // + .listeners(listener) // + .build(); + + session.getLauncher().execute(request); assertThat(errorRef.get()).isNull(); } @@ -367,16 +379,22 @@ void extensionCanReadValueFromSessionStoreAndReadByLauncherSessionListenerOnClos .build(); try (LauncherSession session = LauncherFactory.openSession(config)) { - var launcher = session.getLauncher(); - var request = request().selectors(selectClass(SessionStoringTestCase.class)).build(); AtomicReference errorRef = new AtomicReference<>(); - launcher.execute(request, new TestExecutionListener() { + var listener = new TestExecutionListener() { @Override public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { testExecutionResult.getThrowable().ifPresent(errorRef::set); } - }); + }; + + var request = request() // + .selectors(selectClass(SessionStoringTestCase.class)) // + .forExecution() // + .listeners(listener) // + .build(); + + session.getLauncher().execute(request); assertThat(errorRef.get()).isNull(); } @@ -390,10 +408,12 @@ void sessionResourceClosedOnSessionClose() { .build(); try (LauncherSession session = LauncherFactory.openSession(config)) { - var launcher = session.getLauncher(); - var request = request().selectors(selectClass(SessionResourceAutoCloseTestCase.class)).build(); + var request = request() // + .selectors(selectClass(SessionResourceAutoCloseTestCase.class)) // + .forExecution() // + .build(); - launcher.execute(request); + session.getLauncher().execute(request); assertThat(CloseTrackingResource.closed).isFalse(); } @@ -406,10 +426,12 @@ void requestResourceClosedOnExecutionClose() { var config = LauncherConfig.builder().build(); try (LauncherSession session = LauncherFactory.openSession(config)) { - var launcher = session.getLauncher(); - var request = request().selectors(selectClass(RequestResourceAutoCloseTestCase.class)).build(); + var request = request() // + .selectors(selectClass(RequestResourceAutoCloseTestCase.class)) // + .forExecution() // + .build(); - launcher.execute(request); + session.getLauncher().execute(request); assertThat(CloseTrackingResource.closed).isTrue(); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java index 70e2cb5c6814..212416b25191 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java @@ -34,6 +34,7 @@ class LauncherSessionTests { .build(); LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request().build(); + @SuppressWarnings("deprecation") @Test void callsRegisteredListenersWhenLauncherIsUsedDirectly() { var launcher = LauncherFactory.create(launcherConfig); @@ -60,11 +61,32 @@ void callsRegisteredListenersWhenLauncherIsUsedDirectly() { inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); + + testPlan = launcher.discover(request); + + inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); + inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); + inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); + inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); + + launcher.execute(LauncherExecutionRequestBuilder.request(testPlan).build()); + + inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); + inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); + inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); + inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); + + launcher.execute(LauncherExecutionRequestBuilder.request(request).build()); + + inOrder.verify(firstSessionListener).launcherSessionOpened(launcherSession.capture()); + inOrder.verify(secondSessionListener).launcherSessionOpened(launcherSession.getValue()); + inOrder.verify(secondSessionListener).launcherSessionClosed(launcherSession.getValue()); + inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); } + @SuppressWarnings("deprecation") @Test void callsRegisteredListenersWhenLauncherIsUsedViaSession() { - @SuppressWarnings("resource") var session = LauncherFactory.openSession(launcherConfig); var launcher = session.getLauncher(); @@ -74,15 +96,13 @@ void callsRegisteredListenersWhenLauncherIsUsedViaSession() { verifyNoMoreInteractions(firstSessionListener, secondSessionListener); var testPlan = launcher.discover(request); - - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - launcher.execute(testPlan); - - verifyNoMoreInteractions(firstSessionListener, secondSessionListener); - launcher.execute(request); + testPlan = launcher.discover(request); + launcher.execute(LauncherExecutionRequestBuilder.request(testPlan).build()); + launcher.execute(LauncherExecutionRequestBuilder.request(request).build()); + verifyNoMoreInteractions(firstSessionListener, secondSessionListener); session.close(); @@ -92,9 +112,9 @@ void callsRegisteredListenersWhenLauncherIsUsedViaSession() { verifyNoMoreInteractions(firstSessionListener, secondSessionListener); } + @SuppressWarnings("deprecation") @Test void closedSessionCannotBeUsed() { - @SuppressWarnings("resource") var session = LauncherFactory.openSession(launcherConfig); var launcher = session.getLauncher(); var testPlan = launcher.discover(request); @@ -104,6 +124,10 @@ void closedSessionCannotBeUsed() { assertThrows(PreconditionViolationException.class, () -> launcher.discover(request)); assertThrows(PreconditionViolationException.class, () -> launcher.execute(testPlan)); assertThrows(PreconditionViolationException.class, () -> launcher.execute(request)); + assertThrows(PreconditionViolationException.class, + () -> launcher.execute(LauncherExecutionRequestBuilder.request(testPlan).build())); + assertThrows(PreconditionViolationException.class, + () -> launcher.execute(LauncherExecutionRequestBuilder.request(request).build())); } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/StoreSharingTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/StoreSharingTests.java index 043a88952cda..d84b9b1d5b8d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/StoreSharingTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/StoreSharingTests.java @@ -20,7 +20,7 @@ import org.junit.platform.fakes.TestEngineSpy; import org.junit.platform.fakes.TestEngineStub; import org.junit.platform.launcher.Launcher; -import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherExecutionRequest; /** * @since 5.13 @@ -54,8 +54,9 @@ public void execute(ExecutionRequest request) { .addTestEngines(engineWriter, engineReader) // .build()); - LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder // + LauncherExecutionRequest discoveryRequest = LauncherDiscoveryRequestBuilder // .request() // + .forExecution() // .build(); launcher.execute(discoveryRequest); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java index ee7e27cae5cd..c7a1bfea315f 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java @@ -72,12 +72,14 @@ void interceptsStream(String configParam, Supplier printStreamSuppl }).when(listener).executionStarted(any()); var launcher = createLauncher(engine); - var discoveryRequest = request()// + var executionRequest = request()// .selectors(selectUniqueId(test.getUniqueId()))// .configurationParameter(configParam, String.valueOf(true))// .configurationParameter(LauncherConstants.CAPTURE_MAX_BUFFER_PROPERTY_NAME, String.valueOf(5))// + .forExecution()// + .listeners(listener)// .build(); - launcher.execute(discoveryRequest, listener); + launcher.execute(executionRequest); var testPlanArgumentCaptor = ArgumentCaptor.forClass(TestPlan.class); var inOrder = inOrder(listener); @@ -105,12 +107,14 @@ void doesNotInterceptStreamWhenAlreadyBeingIntercepted(String configParam, assertThat(StreamInterceptor.registerStderr(1)).isPresent(); var launcher = createLauncher(engine); - var discoveryRequest = request()// + var listener = mock(TestExecutionListener.class); + var executionRequest = request()// .selectors(selectUniqueId(test.getUniqueId()))// .configurationParameter(configParam, String.valueOf(true))// + .forExecution()// + .listeners(listener)// .build(); - var listener = mock(TestExecutionListener.class); - launcher.execute(discoveryRequest, listener); + launcher.execute(executionRequest); verify(listener, never()).reportingEntryPublished(any(), any()); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/jfr/FlightRecordingExecutionListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/jfr/FlightRecordingExecutionListenerIntegrationTests.java index 82483f0492af..ffda00f41888 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/jfr/FlightRecordingExecutionListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/jfr/FlightRecordingExecutionListenerIntegrationTests.java @@ -45,6 +45,7 @@ void reportsEvents(@TempDir Path tempDir) { var request = request() // .selectors(selectClass(TestCase.class)) // .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(tempDir)) // + .forExecution() // .build(); launcher.execute(request); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java index fe7c18cdc0fe..75e45185b55c 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java @@ -91,9 +91,12 @@ private static void executeTestCase(LoggingListener listener) { .build(); var request = request() // .selectors(selectClass(TestCase.class)) // - .filters(includeEngines("junit-jupiter")).build(); + .filters(includeEngines("junit-jupiter")) // + .forExecution() // + .listeners(listener) // + .build(); LauncherFactory.create(config) // - .execute(request, listener); + .execute(request); } @SuppressWarnings("JUnitMalformedDeclaration") 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 35fd8cc2692d..e2ed675c0e94 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 @@ -208,13 +208,7 @@ private List executeTests(Map configurationParameters) { private List executeTests(Map configurationParameters, ClassSelector... classSelectors) { List uniqueIds = new ArrayList<>(); - var request = request()// - .selectors(classSelectors)// - .filters(includeEngines("junit-jupiter"))// - .configurationParameters(configurationParameters)// - .configurationParameter(WORKING_DIR_PROPERTY_NAME, workingDir.toAbsolutePath().toString())// - .build(); - LauncherFactory.create().execute(request, new TestExecutionListener() { + var listener = new TestExecutionListener() { @Nullable private TestPlan testPlan; @@ -241,7 +235,16 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult uniqueIds.add(testIdentifier.getUniqueId()); } } - }); + }; + var request = request()// + .selectors(classSelectors)// + .filters(includeEngines("junit-jupiter"))// + .configurationParameters(configurationParameters)// + .configurationParameter(WORKING_DIR_PROPERTY_NAME, workingDir.toAbsolutePath().toString())// + .forExecution()// + .listeners(listener)// + .build(); + LauncherFactory.create().execute(request); return uniqueIds; } diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java index f405a2163637..db97f093ac41 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java @@ -432,8 +432,12 @@ private void executeTests(TestEngine engine, Clock clock) { var reportListener = new LegacyXmlReportGeneratingListener(tempDirectory.toString(), out, clock); var launcher = createLauncher(engine); launcher.registerTestExecutionListeners(reportListener); - launcher.execute(request().configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, - "false").selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))).build()); + var request = request() // + .configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, "false") // + .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // + .forExecution() // + .build(); + launcher.execute(request); } private Match readValidXmlFile(Path xmlFile) throws Exception { diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java index 170243191116..6a869b5ec1ed 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java @@ -304,8 +304,10 @@ private static void executeTests(Path tempDirectory, TestEngine engine, Path out .configurationParameter(CAPTURE_STDERR_PROPERTY_NAME, String.valueOf(true)) // .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, outputDir.toString()) // .configurationParameters(extraConfigurationParameters) // + .forExecution() // + .listeners(new OpenTestReportGeneratingListener(tempDirectory)) // .build(); - createLauncher(engine).execute(request, new OpenTestReportGeneratingListener(tempDirectory)); + createLauncher(engine).execute(request); } } From 6153c3513f861adeaada7a8854fac3b6772e88c2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 7 Jul 2025 10:50:23 +0000 Subject: [PATCH 042/162] Introduce support for cancelling a running execution to the Launcher API (#4728) This commit introduces `CancellationToken` that can be created and passed to the `Launcher` as part of a `LauncherExecutionRequest`. The `Launcher` checks whether cancellation has been requested on the token prior to asking each test engine to execute tests. If cancellation has been requested, the `Launcher` reports all direct children of engine descriptors as skipped. Moreover, it passes the `CancellationToken` to each engine so they can check and respond to cancellation as well which will be implemented separately as indicated by TODO comments. `EngineTestKit` also supports passing a `CancellationToken` to the engine under test. The documentation now contains an example for implementing a fail-fast listener and documents the additional requirement for test engines that wish to support cancellation. Resolves #1880. --- .../src/docs/asciidoc/link-attributes.adoc | 2 + .../release-notes/release-notes-6.0.0-M2.adoc | 11 ++- .../user-guide/advanced-topics/engines.adoc | 7 ++ .../advanced-topics/launcher-api.adoc | 32 +++++++- .../java/example/UsingTheLauncherDemo.java | 76 +++++++++++-------- .../UsingTheLauncherForDiscoveryDemo.java | 57 ++++++++++++++ .../platform/engine/CancellationToken.java | 69 +++++++++++++++++ .../engine/DisabledCancellationToken.java | 31 ++++++++ .../platform/engine/ExecutionRequest.java | 33 ++++++-- .../engine/RegularCancellationToken.java | 31 ++++++++ .../hierarchical/HierarchicalTestEngine.java | 1 + .../launcher/LauncherExecutionRequest.java | 6 ++ .../launcher/core/DefaultLauncher.java | 9 ++- .../core/DefaultLauncherExecutionRequest.java | 11 ++- .../core/EngineExecutionOrchestrator.java | 39 +++++++--- .../core/LauncherExecutionRequestBuilder.java | 19 ++++- .../platform/suite/engine/SuiteLauncher.java | 5 +- .../testkit/engine/EngineTestKit.java | 31 +++++++- .../vintage/engine/VintageTestEngine.java | 1 + .../VintageTestEngineExecutionTests.java | 18 ++--- .../HierarchicalTestExecutorTests.java | 11 +-- .../launcher/core/DefaultLauncherTests.java | 61 +++++++++++++-- .../suite/engine/SuiteEngineTests.java | 9 ++- .../testkit/engine/EngineTestKitTests.java | 24 +++++- 24 files changed, 503 insertions(+), 91 deletions(-) create mode 100644 documentation/src/test/java/example/UsingTheLauncherForDiscoveryDemo.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/DisabledCancellationToken.java create mode 100644 junit-platform-engine/src/main/java/org/junit/platform/engine/RegularCancellationToken.java diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index 70c71f7cba49..d9ee5abf73e5 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -22,6 +22,7 @@ endif::[] // Platform Engine :junit-platform-engine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/package-summary.html[junit-platform-engine] :junit-platform-engine-support-discovery: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/discovery/package-summary.html[org.junit.platform.engine.support.discovery] +:CancellationToken: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/CancellationToken.html[CancellationToken] :ClasspathResourceSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathResourceSelector.html[ClasspathResourceSelector] :ClasspathRootSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClasspathRootSelector.html[ClasspathRootSelector] :ClassSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ClassSelector.html[ClassSelector] @@ -66,6 +67,7 @@ endif::[] :LauncherDiscoveryListener: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryListener.html[LauncherDiscoveryListener] :LauncherDiscoveryRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherDiscoveryRequest.html[LauncherDiscoveryRequest] :LauncherDiscoveryRequestBuilder: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.html[LauncherDiscoveryRequestBuilder] +:LauncherExecutionRequest: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherExecutionRequest.html[LauncherExecutionRequest] :LauncherFactory: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/core/LauncherFactory.html[LauncherFactory] :LauncherInterceptor: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherInterceptor.html[LauncherInterceptor] :LauncherSession: {javadoc-root}/org.junit.platform.launcher/org/junit/platform/launcher/LauncherSession.html[LauncherSession] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 1cc126ffe1fa..90b3be039cfe 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -30,12 +30,17 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements -* Introduce new `Launcher.execute(LauncherExecutionRequest)` API with corresponding +* Introduce new `Launcher.execute({LauncherExecutionRequest})` API with corresponding `LauncherExecutionRequestBuilder` to enable the addition of parameters to test executions without additional overloads of `execute`. * Introduce `LauncherDiscoveryRequestBuilder.forExecution()` method as a convenience - method for constructing a `LauncherExecutionRequest` that contains a - `LauncherDiscoveryRequest`. + method for constructing a `{LauncherExecutionRequest}` that contains a + `{LauncherDiscoveryRequest}`. +* Introduce support for cancelling a running test execution via a `{CancellationToken}` + passed to the `{Launcher}` as part of a `{LauncherExecutionRequest}` and from there to + all registered test engines. Please refer to the + <<../user-guide/index.adoc#launcher-api-launcher-cancellation, User Guide>> for details + and a usage example. * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc index c82204095cd2..03615613456f 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/engines.adoc @@ -120,6 +120,13 @@ compatibility with build tools and IDEs: siblings or other nodes that are required for the execution of the selected tests. * `TestEngines` _should_ support <> tests and containers so that tag filters can be applied when discovering tests. +* [[test-engines-requirements-cancellation]] A `TestEngine` _should_ cancel its execution + when the `{CancellationToken}` it is passed as part of the `ExecutionRequest` indicates + that cancellation has been requested. In this case, it _should_ report any remaining + `TestDescriptors` as skipped but not report any events for their descendants. It _may_ + report already started `TestDescriptors` as aborted in case they have not been executed + completely. If a `TestEngine` supports cancellation, it should clean up any resources + that it has created just like if execution had finished regularly. [[test-engines-discovery-issues]] ==== Reporting Discovery Issues diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc index be61e66fd51d..b2b87db51a2b 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -30,12 +30,12 @@ Usage Example: [source,java,indent=0] ---- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=imports] +include::{testDir}/example/UsingTheLauncherForDiscoveryDemo.java[tags=imports] ---- [source,java,indent=0] ---- -include::{testDir}/example/UsingTheLauncherDemo.java[tags=discovery] +include::{testDir}/example/UsingTheLauncherForDiscoveryDemo.java[tags=discovery] ---- You can select classes, methods, and all classes in a package or even search for all tests @@ -353,3 +353,31 @@ between them. Alternatively, it's possible to inject resources into test engines by <>. + +[[launcher-api-launcher-cancellation]] +==== Cancelling a Running Test Execution + +The launcher API provides the ability to cancel a running test execution mid-flight while +allowing engines to clean up resources. To request an execution to be cancelled, you need +to call `cancel()` on the `{CancellationToken}` that is passed to `Launcher.execute` as +part of the `{LauncherExecutionRequest}`. + +For example, implementing a listener that cancels test execution after the first test +failed can be achieved as follows. + +[source,java,indent=0] +---- +include::{testDir}/example/UsingTheLauncherDemo.java[tags=cancellation] +---- +<1> Create a `{CancellationToken}` +<2> Implement a `{TestExecutionListener}` that calls `cancel()` when a test fails +<3> Register the cancellation token +<4> Register the listener +<5> Pass the `{LauncherExecutionRequest}` to `Launcher.execute` + +WARNING: Cancelling tests relies on <> checking and responding to the +`{CancellationToken}` appropriately (see +<> for details). The +`Launcher` will also check the token and cancel test execution when multiple test engines +are present at runtime. +// TODO #4725 List engines that are known to support cancellation here diff --git a/documentation/src/test/java/example/UsingTheLauncherDemo.java b/documentation/src/test/java/example/UsingTheLauncherDemo.java index b2bbe4a95106..f6ea89febb40 100644 --- a/documentation/src/test/java/example/UsingTheLauncherDemo.java +++ b/documentation/src/test/java/example/UsingTheLauncherDemo.java @@ -10,7 +10,7 @@ package example; -// tag::imports[] +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; @@ -18,8 +18,12 @@ import java.io.PrintWriter; import java.nio.file.Path; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -28,6 +32,7 @@ import org.junit.platform.launcher.LauncherSessionListener; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherConfig; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; @@ -36,42 +41,14 @@ import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; import org.junit.platform.reporting.legacy.xml.LegacyXmlReportGeneratingListener; -// end::imports[] /** * @since 5.0 */ class UsingTheLauncherDemo { - @org.junit.jupiter.api.Test - @SuppressWarnings("unused") - void discovery() { - // @formatter:off - // tag::discovery[] - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() - .selectors( - selectPackage("com.example.mytests"), - selectClass(MyTestClass.class) - ) - .filters( - includeClassNamePatterns(".*Tests") - ) - // end::discovery[] - .configurationParameter("enableHttpServer", "false") - // tag::discovery[] - .build(); - - try (LauncherSession session = LauncherFactory.openSession()) { - TestPlan testPlan = session.getLauncher().discover(request); - - // ... discover additional test plans or execute tests - } - // end::discovery[] - // @formatter:on - } - - @org.junit.jupiter.api.Tag("exclude") - @org.junit.jupiter.api.Test + @Tag("exclude") + @Test @SuppressWarnings("unused") void execution() { // @formatter:off @@ -110,7 +87,7 @@ void execution() { // @formatter:on } - @org.junit.jupiter.api.Test + @Test void launcherConfig() { Path reportsDir = Path.of("target", "xml-reports"); PrintWriter out = new PrintWriter(System.out); @@ -142,6 +119,41 @@ void launcherConfig() { // @formatter:on } + @Test + @SuppressWarnings("unused") + void cancellation() { + // tag::cancellation[] + CancellationToken cancellationToken = CancellationToken.create(); // <1> + + TestExecutionListener failFastListener = new TestExecutionListener() { + @Override + public void executionFinished(TestIdentifier identifier, TestExecutionResult result) { + if (result.getStatus() == FAILED) { + cancellationToken.cancel(); // <2> + } + } + }; + + // end::cancellation[] + // @formatter:off + // tag::cancellation[] + LauncherExecutionRequest executionRequest = LauncherDiscoveryRequestBuilder.request() + .selectors(selectClass(MyTestClass.class)) + .forExecution() + .cancellationToken(cancellationToken) // <3> + .listeners(failFastListener) // <4> + .build(); + // end::cancellation[] + // @formatter:off + // tag::cancellation[] + + try (LauncherSession session = LauncherFactory.openSession()) { + session.getLauncher().execute(executionRequest); // <5> + } + // end::cancellation[] + // @formatter:on + } + } class MyTestClass { diff --git a/documentation/src/test/java/example/UsingTheLauncherForDiscoveryDemo.java b/documentation/src/test/java/example/UsingTheLauncherForDiscoveryDemo.java new file mode 100644 index 000000000000..9f9cbb2a8932 --- /dev/null +++ b/documentation/src/test/java/example/UsingTheLauncherForDiscoveryDemo.java @@ -0,0 +1,57 @@ +/* + * 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 example; + +// tag::imports[] +import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; + +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; +// end::imports[] + +/** + * @since 6.0 + */ +class UsingTheLauncherForDiscoveryDemo { + + @org.junit.jupiter.api.Test + @SuppressWarnings("unused") + void discovery() { + // @formatter:off + // tag::discovery[] + LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + .selectors( + selectPackage("com.example.mytests"), + selectClass(MyTestClass.class) + ) + .filters( + includeClassNamePatterns(".*Tests") + ) + .build(); + + try (LauncherSession session = LauncherFactory.openSession()) { + TestPlan testPlan = session.getLauncher().discover(request); + + // ... discover additional test plans or execute tests + } + // end::discovery[] + // @formatter:on + } + + static class MyTestClass { + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java new file mode 100644 index 000000000000..7990c2fe34c5 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/CancellationToken.java @@ -0,0 +1,69 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * Token that should be checked to determine whether an operation was requested + * to be cancelled. + * + *

    For example, this is used by the + * {@link org.junit.platform.launcher.Launcher} and + * {@link org.junit.platform.engine.TestEngine} implementations to determine + * whether the current test execution should be cancelled. + * + *

    This interface is not intended to be implemented by clients. + * + * @since 6.0 + * @see org.junit.platform.launcher.core.LauncherExecutionRequestBuilder#cancellationToken(CancellationToken) + * @see org.junit.platform.launcher.LauncherExecutionRequest#getCancellationToken() + * @see ExecutionRequest#getCancellationToken() + */ +@API(status = EXPERIMENTAL, since = "6.0") +public sealed interface CancellationToken permits RegularCancellationToken, DisabledCancellationToken { + + /** + * Create a new, uncancelled cancellation token. + */ + static CancellationToken create() { + return new RegularCancellationToken(); + } + + /** + * Get a new cancellation token that cannot be cancelled. + * + *

    This is only useful for cases when a cancellation token is required + * but is not supported or irrelevant, for example, in tests. + */ + static CancellationToken disabled() { + return DisabledCancellationToken.INSTANCE; + } + + /** + * {@return whether cancellation has been requested} + * + *

    Once this method returns {@code true}, it will never return + * {@code false} in a subsequent call. + */ + boolean isCancellationRequested(); + + /** + * Request cancellation. + * + *

    This will call subsequent calls to {@link #isCancellationRequested()} + * to return {@code true}. + */ + void cancel(); + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DisabledCancellationToken.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DisabledCancellationToken.java new file mode 100644 index 000000000000..0816316f4883 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DisabledCancellationToken.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +/** + * @since 6.0 + */ +final class DisabledCancellationToken implements CancellationToken { + + static final DisabledCancellationToken INSTANCE = new DisabledCancellationToken(); + + private DisabledCancellationToken() { + } + + @Override + public boolean isCancellationRequested() { + return false; + } + + @Override + public void cancel() { + } +} 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 620f6057b94b..cc0a671a0eb0 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 @@ -43,21 +43,22 @@ public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; - private final @Nullable OutputDirectoryProvider outputDirectoryProvider; - private final @Nullable NamespacedHierarchicalStore requestLevelStore; + private final CancellationToken cancellationToken; @Deprecated @API(status = DEPRECATED, since = "1.11") public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { - this(rootTestDescriptor, engineExecutionListener, configurationParameters, null, null); + this(rootTestDescriptor, engineExecutionListener, configurationParameters, null, null, + CancellationToken.disabled()); } private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, @Nullable OutputDirectoryProvider outputDirectoryProvider, - @Nullable NamespacedHierarchicalStore requestLevelStore) { + @Nullable NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { + this.rootTestDescriptor = Preconditions.notNull(rootTestDescriptor, "rootTestDescriptor must not be null"); this.engineExecutionListener = Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); @@ -65,6 +66,8 @@ private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListe "configurationParameters must not be null"); this.outputDirectoryProvider = outputDirectoryProvider; this.requestLevelStore = requestLevelStore; + this.cancellationToken = Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); + ; } /** @@ -100,16 +103,18 @@ public static ExecutionRequest create(TestDescriptor rootTestDescriptor, * @param requestLevelStore {@link NamespacedHierarchicalStore} for storing * request-scoped data; never {@code null} * @return a new {@code ExecutionRequest}; never {@code null} - * @since 1.13 + * @since 6.0 */ - @API(status = INTERNAL, since = "1.13") + @API(status = INTERNAL, since = "6.0") public static ExecutionRequest create(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, - OutputDirectoryProvider outputDirectoryProvider, NamespacedHierarchicalStore requestLevelStore) { + OutputDirectoryProvider outputDirectoryProvider, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters, Preconditions.notNull(outputDirectoryProvider, "outputDirectoryProvider must not be null"), - Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null")); + Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"), + Preconditions.notNull(cancellationToken, "cancellationToken must not be null")); } /** @@ -171,4 +176,16 @@ public NamespacedHierarchicalStore getStore() { "No NamespacedHierarchicalStore was configured for this request"); } + /** + * {@return the {@link CancellationToken} for this request for engines to + * check whether they should cancel execution} + * + * @since 6.0 + * @see CancellationToken#isCancellationRequested() + */ + @API(status = EXPERIMENTAL, since = "6.0") + public CancellationToken getCancellationToken() { + return cancellationToken; + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/RegularCancellationToken.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/RegularCancellationToken.java new file mode 100644 index 000000000000..1a07c32b7f66 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/RegularCancellationToken.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @since 6.0 + */ +final class RegularCancellationToken implements CancellationToken { + + private final AtomicBoolean cancelled = new AtomicBoolean(); + + @Override + public boolean isCancellationRequested() { + return cancelled.get(); + } + + @Override + public void cancel() { + cancelled.set(true); + } +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java index 92cb86129952..f49f0cbcef12 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java @@ -50,6 +50,7 @@ public final void execute(ExecutionRequest request) { try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { C executionContext = createExecutionContext(request); ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request); + // TODO #4725 Provide cancellation support for implementations of HierarchicalTestEngine new HierarchicalTestExecutor<>(request, executionContext, executorService, throwableCollectorFactory).execute().get(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java index f58ef172b312..474913fc9971 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java @@ -16,6 +16,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.junit.platform.engine.CancellationToken; /** * {@code LauncherExecutionRequest} encapsulates a request for test execution @@ -61,4 +62,9 @@ public interface LauncherExecutionRequest { */ Collection getAdditionalTestExecutionListeners(); + /** + * {@return the cancellation token for this execution request} + */ + CancellationToken getCancellationToken(); + } 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 92a72e6e9b51..d9ccb21d423d 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 @@ -18,6 +18,7 @@ import java.util.Collection; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @@ -94,16 +95,18 @@ public void execute(LauncherExecutionRequest launcherExecutionRequest) { "Either a TestPlan or LauncherDiscoveryRequest must be present in the LauncherExecutionRequest"); return InternalTestPlan.from(discover(launcherExecutionRequest.getDiscoveryRequest().get(), EXECUTION)); }); - execute(testPlan, launcherExecutionRequest.getAdditionalTestExecutionListeners()); + execute(testPlan, launcherExecutionRequest.getAdditionalTestExecutionListeners(), + launcherExecutionRequest.getCancellationToken()); } private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, LauncherPhase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } - private void execute(InternalTestPlan internalTestPlan, Collection listeners) { + private void execute(InternalTestPlan internalTestPlan, Collection listeners, + CancellationToken cancellationToken) { try (NamespacedHierarchicalStore requestLevelStore = createRequestLevelStore()) { - executionOrchestrator.execute(internalTestPlan, requestLevelStore, listeners); + executionOrchestrator.execute(internalTestPlan, requestLevelStore, listeners, cancellationToken); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java index 6c270afbe1bb..7992b27ceaa9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.jspecify.annotations.Nullable; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.TestExecutionListener; @@ -28,12 +29,14 @@ final class DefaultLauncherExecutionRequest implements LauncherExecutionRequest private final @Nullable LauncherDiscoveryRequest discoveryRequest; private final @Nullable TestPlan testPlan; private final List executionListeners; + private final CancellationToken cancellationToken; DefaultLauncherExecutionRequest(@Nullable LauncherDiscoveryRequest discoveryRequest, @Nullable TestPlan testPlan, - Collection executionListeners) { + Collection executionListeners, CancellationToken cancellationToken) { this.discoveryRequest = discoveryRequest; this.testPlan = testPlan; this.executionListeners = List.copyOf(executionListeners); + this.cancellationToken = cancellationToken; } @Override @@ -50,4 +53,10 @@ public Optional getTestPlan() { public Collection getAdditionalTestExecutionListeners() { return executionListeners; } + + @Override + public CancellationToken getCancellationToken() { + return cancellationToken; + } + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index e4c5821be3cc..848dd8931211 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -24,6 +24,7 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; @@ -57,13 +58,13 @@ public EngineExecutionOrchestrator() { } void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore, - Collection listeners) { + Collection listeners, CancellationToken cancellationToken) { ConfigurationParameters configurationParameters = internalTestPlan.getConfigurationParameters(); ListenerRegistry testExecutionListenerListeners = buildListenerRegistryForExecution( listeners); withInterceptedStreams(configurationParameters, testExecutionListenerListeners, testExecutionListener -> execute(internalTestPlan, EngineExecutionListener.NOOP, testExecutionListener, - requestLevelStore)); + requestLevelStore, cancellationToken)); } /** @@ -75,18 +76,23 @@ void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore) { + TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { + Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); Preconditions.notNull(testExecutionListener, "testExecutionListener must not be null"); Preconditions.notNull(requestLevelStore, "requestLevelStore must not be null"); + Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); InternalTestPlan internalTestPlan = InternalTestPlan.from(discoveryResult); - execute(internalTestPlan, engineExecutionListener, testExecutionListener, requestLevelStore); + execute(internalTestPlan, engineExecutionListener, testExecutionListener, requestLevelStore, cancellationToken); } private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener parentEngineExecutionListener, - TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore) { + TestExecutionListener testExecutionListener, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { + internalTestPlan.markStarted(); // Do not directly pass the internal test plan to test execution listeners. @@ -101,7 +107,7 @@ private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener else { execute(discoveryResult, buildEngineExecutionListener(parentEngineExecutionListener, testExecutionListener, testPlan), - requestLevelStore); + requestLevelStore, cancellationToken); } testExecutionListener.testPlanExecutionFinished(testPlan); } @@ -162,7 +168,7 @@ private void withInterceptedStreams(ConfigurationParameters configurationParamet */ @API(status = INTERNAL, since = "1.7", consumers = { "org.junit.platform.testkit" }) public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener engineExecutionListener, - NamespacedHierarchicalStore requestLevelStore) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { Preconditions.notNull(discoveryResult, "discoveryResult must not be null"); Preconditions.notNull(engineExecutionListener, "engineExecutionListener must not be null"); @@ -170,7 +176,7 @@ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionList EngineExecutionListener listener = selectExecutionListener(engineExecutionListener, configurationParameters); for (TestEngine testEngine : discoveryResult.getTestEngines()) { - failOrExecuteEngine(discoveryResult, listener, testEngine, requestLevelStore); + failOrExecuteEngine(discoveryResult, listener, testEngine, requestLevelStore, cancellationToken); } } @@ -185,7 +191,9 @@ private static EngineExecutionListener selectExecutionListener(EngineExecutionLi } private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, EngineExecutionListener listener, - TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore) { + TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { + EngineResultInfo engineDiscoveryResult = discoveryResult.getEngineResult(testEngine); DiscoveryIssueNotifier discoveryIssueNotifier = shouldReportDiscoveryIssues(discoveryResult) // ? engineDiscoveryResult.getDiscoveryIssueNotifier() // @@ -201,9 +209,15 @@ private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, Engine discoveryIssueNotifier.logNonCriticalIssues(testEngine); listener.executionFinished(engineDescriptor, TestExecutionResult.failed(failure)); } + else if (cancellationToken.isCancellationRequested()) { + listener.executionStarted(engineDescriptor); + engineDescriptor.getChildren().forEach(child -> listener.executionSkipped(child, "Execution cancelled")); + listener.executionFinished(engineDescriptor, TestExecutionResult.aborted(null)); + } else { executeEngine(engineDescriptor, listener, discoveryResult.getConfigurationParameters(), testEngine, - discoveryResult.getOutputDirectoryProvider(), discoveryIssueNotifier, requestLevelStore); + discoveryResult.getOutputDirectoryProvider(), discoveryIssueNotifier, requestLevelStore, + cancellationToken); } } @@ -224,12 +238,13 @@ private ListenerRegistry buildListenerRegistryForExecutio private void executeEngine(TestDescriptor engineDescriptor, EngineExecutionListener listener, ConfigurationParameters configurationParameters, TestEngine testEngine, OutputDirectoryProvider outputDirectoryProvider, DiscoveryIssueNotifier discoveryIssueNotifier, - NamespacedHierarchicalStore requestLevelStore) { + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { + OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, engineDescriptor); try { testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters, - outputDirectoryProvider, requestLevelStore)); + outputDirectoryProvider, requestLevelStore, cancellationToken)); discoveryIssueNotifier.logNonCriticalIssues(testEngine); delayingListener.reportEngineOutcome(); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java index ebf01f10dfa5..161800c6aeca 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java @@ -10,6 +10,7 @@ package org.junit.platform.launcher.core; +import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.ArrayList; @@ -19,6 +20,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.TestExecutionListener; @@ -59,6 +61,7 @@ public static LauncherExecutionRequestBuilder request(TestPlan testPlan) { private final @Nullable LauncherDiscoveryRequest discoveryRequest; private final @Nullable TestPlan testPlan; private final Collection executionListeners = new ArrayList<>(); + private @Nullable CancellationToken cancellationToken; private LauncherExecutionRequestBuilder(@Nullable LauncherDiscoveryRequest discoveryRequest, @Nullable TestPlan testPlan) { @@ -82,12 +85,26 @@ public LauncherExecutionRequestBuilder listeners(TestExecutionListener... listen return this; } + /** + * Set the cancellation token for the request. + * + * @param cancellationToken the {@code CancellationToken} to use; never + * {@code null}. + * @return this builder for method chaining + * @see CancellationToken + */ + public LauncherExecutionRequestBuilder cancellationToken(CancellationToken cancellationToken) { + this.cancellationToken = Preconditions.notNull(cancellationToken, "CancellationToken must not be null"); + return this; + } + /** * Build the {@link LauncherExecutionRequest} that has been configured via * this builder. */ public LauncherExecutionRequest build() { - return new DefaultLauncherExecutionRequest(discoveryRequest, testPlan, executionListeners); + return new DefaultLauncherExecutionRequest(this.discoveryRequest, this.testPlan, this.executionListeners, + requireNonNullElseGet(this.cancellationToken, CancellationToken::disabled)); } } diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java index 640d90f984e1..eb830c130507 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java @@ -16,6 +16,7 @@ import java.util.Set; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; @@ -62,7 +63,9 @@ TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener parentEngineExecutionListener, NamespacedHierarchicalStore requestLevelStore) { SummaryGeneratingListener listener = new SummaryGeneratingListener(); - executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore); + // TODO #4725 Provide cancellation support for Suite engine + executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore, + CancellationToken.disabled()); return listener.getSummary(); } 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 b12e781325bb..373335beff74 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 @@ -12,6 +12,7 @@ import static java.util.Collections.emptySet; import static java.util.Collections.singleton; +import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -30,6 +31,7 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoverySelector; @@ -244,16 +246,20 @@ public static EngineExecutionResults execute(TestEngine testEngine, LauncherDisc Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); ExecutionRecorder executionRecorder = new ExecutionRecorder(); - executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder); + executeUsingLauncherOrchestration(testEngine, discoveryRequest, executionRecorder, + CancellationToken.disabled()); return executionRecorder.getExecutionResults(); } private static void executeUsingLauncherOrchestration(TestEngine testEngine, - LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { + LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener, + CancellationToken cancellationToken) { + LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); - withRequestLevelStore(store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store)); + withRequestLevelStore( + store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store, cancellationToken)); } private static void withRequestLevelStore(Consumer> action) { @@ -309,6 +315,7 @@ public static final class Builder { .enableImplicitConfigurationParameters(false) // .outputDirectoryProvider(DisabledOutputDirectoryProvider.INSTANCE); private final TestEngine testEngine; + private @Nullable CancellationToken cancellationToken; private Builder(TestEngine testEngine) { this.testEngine = testEngine; @@ -430,6 +437,21 @@ public Builder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryPr return this; } + /** + * Set the {@link CancellationToken} to use for execution. + * + * @param cancellationToken the cancellation token to use; never + * {@code null} + * @return this builder for method chaining + * @since 6.0 + * @see CancellationToken + */ + @API(status = EXPERIMENTAL, since = "6.0") + public Builder cancellationToken(CancellationToken cancellationToken) { + this.cancellationToken = Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); + return this; + } + /** * Discover tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, @@ -464,7 +486,8 @@ public EngineDiscoveryResults discover() { public EngineExecutionResults execute() { LauncherDiscoveryRequest request = this.requestBuilder.build(); ExecutionRecorder executionRecorder = new ExecutionRecorder(); - EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder); + EngineTestKit.executeUsingLauncherOrchestration(this.testEngine, request, executionRecorder, + requireNonNullElseGet(this.cancellationToken, CancellationToken::disabled)); return executionRecorder.getExecutionResults(); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java index fdb8e4c8c2de..5bd0ee01e6a4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java @@ -69,6 +69,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId public void execute(ExecutionRequest request) { EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); + // TODO #4725 Provide cancellation support for Vintage engine engineExecutionListener.executionStarted(engineDescriptor); new VintageExecutor(engineDescriptor, engineExecutionListener, request).executeAllChildren(); engineExecutionListener.executionFinished(engineDescriptor, successful()); diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index 7044e3f5076a..d8c7d4b75ca7 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -16,8 +16,6 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore; -import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; @@ -34,6 +32,8 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.runner.Description.createSuiteDescription; import static org.junit.runner.Description.createTestDescription; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.math.BigDecimal; @@ -47,7 +47,6 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -938,12 +937,13 @@ private static EngineExecutionResults execute(LauncherDiscoveryRequest request) @SuppressWarnings("deprecation") private static void execute(Class testClass, EngineExecutionListener listener) { - TestEngine testEngine = new VintageTestEngine(); - var discoveryRequest = request(testClass); - var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); - testEngine.execute( - ExecutionRequest.create(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters(), - dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore())); + var testEngine = new VintageTestEngine(); + var engineTestDescriptor = testEngine.discover(request(testClass), UniqueId.forEngine(testEngine.getId())); + ExecutionRequest executionRequest = mock(); + when(executionRequest.getRootTestDescriptor()).thenReturn(engineTestDescriptor); + when(executionRequest.getEngineExecutionListener()).thenReturn(listener); + when(executionRequest.getConfigurationParameters()).thenReturn(mock()); + testEngine.execute(executionRequest); } private static LauncherDiscoveryRequest request(Class testClass) { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index 85fb2c028746..b97839efc8c6 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -18,8 +18,6 @@ import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import static org.junit.platform.engine.TestExecutionResult.successful; -import static org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore; -import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; @@ -29,6 +27,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.LENIENT; import java.util.Map; import java.util.Set; @@ -39,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.ThrowingConsumer; -import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -53,6 +51,7 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.stubbing.Answer; import org.opentest4j.TestAbortedException; @@ -80,8 +79,9 @@ void init() { private HierarchicalTestExecutor createExecutor( HierarchicalTestExecutorService executorService) { - var request = ExecutionRequest.create(root, listener, mock(ConfigurationParameters.class), - dummyOutputDirectoryProvider(), dummyNamespacedHierarchicalStore()); + ExecutionRequest request = mock(); + when(request.getRootTestDescriptor()).thenReturn(root); + when(request.getEngineExecutionListener()).thenReturn(listener); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } @@ -552,6 +552,7 @@ void executesDynamicTestDescriptorsWithCustomListener() { } @Test + @MockitoSettings(strictness = LENIENT) void canAbortExecutionOfDynamicChild() throws Exception { var leafUniqueId = UniqueId.root("leaf", "child leaf"); 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 53ea28f3395a..33bb887bd4e2 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 @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import static org.junit.platform.engine.SelectorResolutionResult.unresolved; +import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; @@ -31,11 +32,14 @@ import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -55,6 +59,7 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.DiscoverySelector; @@ -984,11 +989,57 @@ public void execute(ExecutionRequest request) { assertThat(listener.stream(DiscoveryIssueNotifier.class, Level.WARNING)).hasSize(1); } + @Test + void reportsChildrenOfEngineDescriptorAsSkippedAfterCancellationWasRequested() { + var engine = spy(new TestEngineStub("engine-id") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var engineDescriptor = new EngineDescriptor(uniqueId, "Engine"); + var container = new TestDescriptorStub(uniqueId.append("container", "1"), "Container"); + var test = new TestDescriptorStub(container.getUniqueId().append("test", "1"), "Test"); + container.addChild(test); + engineDescriptor.addChild(container); + return engineDescriptor; + } + }); + + var executionListener = mock(TestExecutionListener.class); + + var cancellationToken = CancellationToken.create(); + cancellationToken.cancel(); + + execute(engine, identity(), executionRequest -> executionRequest // + .listeners(executionListener) // + .cancellationToken(cancellationToken)); + + verify(engine, never()).execute(any()); + + var inOrder = inOrder(executionListener); + inOrder.verify(executionListener).testPlanExecutionStarted(any()); + inOrder.verify(executionListener).executionStarted( + argThat(d -> d.getUniqueIdObject().equals(UniqueId.forEngine("engine-id")))); + inOrder.verify(executionListener).executionSkipped( + argThat(d -> d.getUniqueIdObject().getLastSegment().getType().equals("container")), + eq("Execution cancelled")); + inOrder.verify(executionListener).executionFinished( + argThat(d -> d.getUniqueIdObject().equals(UniqueId.forEngine("engine-id"))), + argThat(result -> result.getStatus() == ABORTED)); + inOrder.verify(executionListener).testPlanExecutionFinished(any()); + inOrder.verifyNoMoreInteractions(); + } + private static ReportedData execute(TestEngine engine) { return execute(engine, identity()); } - private static ReportedData execute(TestEngine engine, UnaryOperator configurer) { + private static ReportedData execute(TestEngine engine, + UnaryOperator discoveryConfigurer) { + return execute(engine, discoveryConfigurer, identity()); + } + + private static ReportedData execute(TestEngine engine, + UnaryOperator discoveryConfigurer, + UnaryOperator executionConfigurer) { var executionListener = mock(TestExecutionListener.class); AtomicReference startTime = new AtomicReference<>(); @@ -1009,12 +1060,12 @@ private static ReportedData execute(TestEngine engine, UnaryOperator requestLevelStore = NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore(); - ExecutionRequest request = ExecutionRequest.create(engineDescriptor, listener, - mock(ConfigurationParameters.class), mock(OutputDirectoryProvider.class), requestLevelStore); + ExecutionRequest request = mock(); + when(request.getRootTestDescriptor()).thenReturn(engineDescriptor); + when(request.getEngineExecutionListener()).thenReturn(listener); + when(request.getStore()).thenReturn(requestLevelStore); new SuiteTestEngine().execute(request); diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java index dffb0255fe7d..a27932497a5b 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java @@ -15,6 +15,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.mockito.ArgumentCaptor.forClass; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; import static org.mockito.Mockito.verify; @@ -30,8 +31,12 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.platform.engine.CancellationToken; +import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; import org.junit.platform.engine.support.store.Namespace; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryListener; @@ -80,7 +85,7 @@ void verifyRequestLevelStoreIsUsedInExecution() { ArgumentCaptor> storeCaptor = forClass( NamespacedHierarchicalStore.class); - verify(mockOrchestrator).execute(any(), any(), storeCaptor.capture()); + verify(mockOrchestrator).execute(any(), any(), storeCaptor.capture(), eq(CancellationToken.disabled())); assertNotNull(storeCaptor.getValue(), "Request level store should be passed to execute"); } } @@ -94,6 +99,23 @@ void usesImplicitConfigurationParametersWhenEnabled(boolean enabled, String expe assertThat(value).isEqualTo(Optional.ofNullable(expectedValue)); } + @Test + void cancellationTokenIsPassedToEngines() { + TestEngine testEngine = mock(TestEngine.class); + when(testEngine.getId()).thenReturn("test-engine"); + when(testEngine.discover(any(), any())).thenReturn( + new EngineDescriptor(UniqueId.forEngine("test-engine"), "Engine")); + + var cancellationToken = CancellationToken.create(); + + EngineTestKit.engine(testEngine).cancellationToken(cancellationToken).execute(); + + var executionRequest = forClass(ExecutionRequest.class); + verify(testEngine).execute(executionRequest.capture()); + + assertThat(executionRequest.getValue().getCancellationToken()).isSameAs(cancellationToken); + } + private Optional executeExampleTestCaseAndCollectValue(UnaryOperator configuration) { return configuration.apply(EngineTestKit.engine("junit-jupiter")) // .selectors(selectClass(ExampleTestCase.class)) // From 8ab24ee84a7a5032f57843bc31010448c9050cc4 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 7 Jul 2025 11:21:06 +0000 Subject: [PATCH 043/162] Provide cancellation support for HierarchicalTestEngine implementations (#4729) This adds support for cancellation to the JUnit Jupiter engine but also third-party implementations of `HierarchicalTestEngine` such as Spock and Cucumber. Issue: #4725 --- .../release-notes/release-notes-6.0.0-M2.adoc | 2 + .../advanced-topics/launcher-api.adoc | 12 +- .../hierarchical/HierarchicalTestEngine.java | 5 +- .../HierarchicalTestExecutor.java | 20 ++- .../support/hierarchical/NodeTestTask.java | 16 +- .../hierarchical/NodeTestTaskContext.java | 7 +- .../AbstractJupiterTestEngineTests.java | 8 + .../engine/ExecutionCancellationTests.java | 149 ++++++++++++++++++ .../HierarchicalTestExecutorTests.java | 64 ++++++++ 9 files changed, 268 insertions(+), 15 deletions(-) create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 90b3be039cfe..f6f48e5bbea4 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -41,6 +41,8 @@ repository on GitHub. all registered test engines. Please refer to the <<../user-guide/index.adoc#launcher-api-launcher-cancellation, User Guide>> for details and a usage example. +* Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as + JUnit Jupiter, Spock, and Cucumber. * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc index b2b87db51a2b..35c8fe642cf5 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -375,9 +375,17 @@ include::{testDir}/example/UsingTheLauncherDemo.java[tags=cancellation] <4> Register the listener <5> Pass the `{LauncherExecutionRequest}` to `Launcher.execute` -WARNING: Cancelling tests relies on <> checking and responding to the +[NOTE] +.Test Engine Support for Cancellation +==== +Cancelling tests relies on <> checking and responding to the `{CancellationToken}` appropriately (see <> for details). The `Launcher` will also check the token and cancel test execution when multiple test engines are present at runtime. -// TODO #4725 List engines that are known to support cancellation here + +At the time of writing the following test engines support cancellation: + +* `{junit-jupiter-engine}` +* Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber +==== diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java index f49f0cbcef12..c68193d13bb5 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java @@ -15,6 +15,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestEngine; @@ -41,6 +42,9 @@ public HierarchicalTestEngine() { * its {@linkplain ExecutionRequest#getEngineExecutionListener() execution * listener} of test execution events. * + *

    Supports cancellation via the {@link CancellationToken} passed in the + * supplied {@code request}. + * * @see Node * @see #createExecutorService * @see #createExecutionContext @@ -50,7 +54,6 @@ public final void execute(ExecutionRequest request) { try (HierarchicalTestExecutorService executorService = createExecutorService(request)) { C executionContext = createExecutionContext(request); ThrowableCollector.Factory throwableCollectorFactory = createThrowableCollectorFactory(request); - // TODO #4725 Provide cancellation support for implementations of HierarchicalTestEngine new HierarchicalTestExecutor<>(request, executionContext, executorService, throwableCollectorFactory).execute().get(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java index 68549dbf632a..9fb2ac27e2c9 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java @@ -13,6 +13,7 @@ import java.util.concurrent.Future; import org.jspecify.annotations.Nullable; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -48,14 +49,23 @@ class HierarchicalTestExecutor { } Future<@Nullable Void> execute() { + return this.executorService.submit(createRootTestTask()); + } + + private NodeTestTask createRootTestTask() { + NodeTestTaskContext taskContext = createTaskContext(); TestDescriptor rootTestDescriptor = this.request.getRootTestDescriptor(); - EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); - NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(rootTestDescriptor); - NodeTestTaskContext taskContext = new NodeTestTaskContext(executionListener, this.executorService, - this.throwableCollectorFactory, executionAdvisor); NodeTestTask rootTestTask = new NodeTestTask<>(taskContext, rootTestDescriptor); rootTestTask.setParentContext(this.rootContext); - return this.executorService.submit(rootTestTask); + return rootTestTask; + } + + private NodeTestTaskContext createTaskContext() { + EngineExecutionListener executionListener = this.request.getEngineExecutionListener(); + NodeExecutionAdvisor executionAdvisor = new NodeTreeWalker().walk(this.request.getRootTestDescriptor()); + CancellationToken cancellationToken = this.request.getCancellationToken(); + return new NodeTestTaskContext(executionListener, this.executorService, this.throwableCollectorFactory, + executionAdvisor, cancellationToken); } } 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 6fb309dbab65..321c3df0027b 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 @@ -49,6 +49,8 @@ class NodeTestTask implements TestTask { private static final Runnable NOOP = () -> { }; + static final SkipResult CANCELLED_SKIP_RESULT = SkipResult.skip("Execution cancelled"); + private final NodeTestTaskContext taskContext; private final TestDescriptor testDescriptor; private final Node node; @@ -104,9 +106,11 @@ void setParentContext(@Nullable C parentContext) { public void execute() { try { throwableCollector = taskContext.throwableCollectorFactory().create(); - prepare(); + if (!taskContext.cancellationToken().isCancellationRequested()) { + prepare(); + } if (throwableCollector.isEmpty()) { - checkWhetherSkipped(); + throwableCollector.execute(() -> skipResult = checkWhetherSkipped()); } if (throwableCollector.isEmpty() && !requiredSkipResult().isSkipped()) { executeRecursively(); @@ -144,8 +148,10 @@ private void prepare() { parentContext = null; } - private void checkWhetherSkipped() { - requiredThrowableCollector().execute(() -> skipResult = node.shouldBeSkipped(requiredContext())); + private SkipResult checkWhetherSkipped() throws Exception { + return taskContext.cancellationToken().isCancellationRequested() // + ? CANCELLED_SKIP_RESULT // + : node.shouldBeSkipped(requiredContext()); } private void executeRecursively() { @@ -193,7 +199,7 @@ private void reportCompletion() { if (throwableCollector.isEmpty() && requiredSkipResult().isSkipped()) { var skipResult = requiredSkipResult(); try { - node.nodeSkipped(requiredContext(), testDescriptor, skipResult); + node.nodeSkipped(requireNonNullElse(context, parentContext), testDescriptor, skipResult); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java index 005d8ab6d02c..d5dbc34c5e09 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java @@ -10,19 +10,22 @@ package org.junit.platform.engine.support.hierarchical; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; /** * @since 1.3.1 */ record NodeTestTaskContext(EngineExecutionListener listener, HierarchicalTestExecutorService executorService, - ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor) { + ThrowableCollector.Factory throwableCollectorFactory, NodeExecutionAdvisor executionAdvisor, + CancellationToken cancellationToken) { NodeTestTaskContext withListener(EngineExecutionListener listener) { if (this.listener == listener) { return this; } - return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor); + return new NodeTestTaskContext(listener, executorService, throwableCollectorFactory, executionAdvisor, + cancellationToken); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 8b6b7c5ae009..02d084aa65be 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -97,6 +97,14 @@ protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequest request) return EngineTestKit.discover(this.engine, request); } + protected EngineTestKit.Builder jupiterTestEngine() { + return EngineTestKit.engine(this.engine) // + .outputDirectoryProvider(dummyOutputDirectoryProvider()) // + .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)) // + .configurationParameter(CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, Severity.INFO.name()) // + .enableImplicitConfigurationParameters(false); + } + protected static LauncherDiscoveryRequestBuilder defaultRequest() { return request() // .outputDirectoryProvider(dummyOutputDirectoryProvider()) // diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java new file mode 100644 index 000000000000..319f6d63b398 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExecutionCancellationTests.java @@ -0,0 +1,149 @@ +/* + * 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.jupiter.engine; + +import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; +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; +import static org.junit.platform.testkit.engine.EventConditions.reportEntry; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import java.util.Map; +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.DynamicNode; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.TestReporter; +import org.junit.platform.engine.CancellationToken; + +class ExecutionCancellationTests extends AbstractJupiterTestEngineTests { + + @BeforeEach + void initializeCancellationToken() { + TestCase.cancellationToken = CancellationToken.create(); + } + + @AfterEach + void resetCancellationToken() { + TestCase.cancellationToken = null; + } + + @Test + void canCancelExecutionWhileTestClassIsRunning() { + var testClass = RegularTestCase.class; + + var results = jupiterTestEngine() // + .selectors(selectClass(testClass)) // + .cancellationToken(TestCase.requiredCancellationToken()) // + .execute(); + + results.testEvents().assertStatistics(stats -> stats.started(1).finished(1).skipped(1)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(test("first"), started()), // + event(test("first"), reportEntry(Map.of("cancelled", "true"))), // + event(test("first"), finishedSuccessfully()), // + event(test("second"), skippedWithReason("Execution cancelled")), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + @Test + void canCancelExecutionWhileDynamicTestsAreRunning() { + var testClass = DynamicTestCase.class; + + var results = jupiterTestEngine() // + .selectors(selectClass(testClass)) // + .cancellationToken(TestCase.requiredCancellationToken()) // + .execute(); + + results.containerEvents().assertStatistics(stats -> stats.skipped(1)); + results.testEvents().assertStatistics(stats -> stats.started(1).finished(1).skipped(0)); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(testClass), started()), // + event(container("testFactory"), started()), // + event(dynamicTestRegistered("#1"), displayName("first")), // + event(test("#1"), started()), // + event(test("#1"), finishedSuccessfully()), // + event(dynamicTestRegistered("#2"), displayName("container")), // + event(container("#2"), skippedWithReason("Execution cancelled")), // + event(container("testFactory"), finishedSuccessfully()), // + event(container(testClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + + static class TestCase { + + static @Nullable CancellationToken cancellationToken; + + static CancellationToken requiredCancellationToken() { + return requireNonNull(cancellationToken); + } + + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @TestMethodOrder(OrderAnnotation.class) + static class RegularTestCase extends TestCase { + + @Test + @Order(1) + void first() { + requiredCancellationToken().cancel(); + } + + @AfterEach + void afterEach(TestReporter reporter) { + reporter.publishEntry("cancelled", String.valueOf(requiredCancellationToken().isCancellationRequested())); + } + + @Test + @Order(2) + void second() { + fail("should not be called"); + } + } + + static class DynamicTestCase extends TestCase { + + @TestFactory + Stream testFactory() { + return Stream.of( // + dynamicTest("first", () -> requiredCancellationToken().cancel()), // + dynamicContainer("container", Stream.of( // + dynamicTest("second", () -> fail("should not be called")) // + )) // + ); + } + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index b97839efc8c6..2602b22e5d5f 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -69,6 +70,8 @@ class HierarchicalTestExecutorTests { @Mock EngineExecutionListener listener; + CancellationToken cancellationToken = CancellationToken.create(); + MyEngineExecutionContext rootContext = new MyEngineExecutionContext(); HierarchicalTestExecutor executor; @@ -82,6 +85,7 @@ private HierarchicalTestExecutor createExecutor( ExecutionRequest request = mock(); when(request.getRootTestDescriptor()).thenReturn(root); when(request.getEngineExecutionListener()).thenReturn(listener); + when(request.getCancellationToken()).thenReturn(cancellationToken); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } @@ -707,6 +711,66 @@ void exceptionInAfterIsReportedInsteadOfEarlierTestAbortedException() throws Exc exceptionInAfter).hasSuppressedException(exceptionInExecute); } + @Test + void reportsNodeAsSkippedWhenCancelledPriorToExecution() { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); + root.addChild(child); + + cancellationToken.cancel(); + executor.execute(); + + var inOrder = inOrder(listener, root, child); + inOrder.verify(root).nodeSkipped(rootContext, root, NodeTestTask.CANCELLED_SKIP_RESULT); + inOrder.verify(listener).executionSkipped(root, NodeTestTask.CANCELLED_SKIP_RESULT.getReason().orElseThrow()); + inOrder.verifyNoMoreInteractions(); + } + + @Test + void reportsNodeAsSkippedWhenCancelledDuringPrepare() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); + root.addChild(child); + + when(root.prepare(any())).thenAnswer(invocation -> { + cancellationToken.cancel(); + return invocation.callRealMethod(); + }); + + executor.execute(); + + var inOrder = inOrder(listener, root, child); + inOrder.verify(root).prepare(rootContext); + inOrder.verify(root).nodeSkipped(rootContext, root, NodeTestTask.CANCELLED_SKIP_RESULT); + inOrder.verify(listener).executionSkipped(root, NodeTestTask.CANCELLED_SKIP_RESULT.getReason().orElseThrow()); + inOrder.verifyNoMoreInteractions(); + } + + @Test + void reportsNodeAsSkippedWhenCancelledDuringBefore() throws Exception { + + var child = spy(new MyLeaf(UniqueId.root("leaf", "child container"))); + root.addChild(child); + + when(root.before(any())).thenAnswer(invocation -> { + cancellationToken.cancel(); + return invocation.callRealMethod(); + }); + + executor.execute(); + + var inOrder = inOrder(listener, root, child); + inOrder.verify(listener).executionStarted(root); + inOrder.verify(root).before(any()); + inOrder.verify(root).execute(any(), any()); + inOrder.verify(child).nodeSkipped(any(), eq(child), eq(NodeTestTask.CANCELLED_SKIP_RESULT)); + inOrder.verify(listener).executionSkipped(child, NodeTestTask.CANCELLED_SKIP_RESULT.getReason().orElseThrow()); + inOrder.verify(root).after(any()); + inOrder.verify(root).cleanUp(any()); + inOrder.verify(listener).executionFinished(root, TestExecutionResult.successful()); + inOrder.verifyNoMoreInteractions(); + } + // ------------------------------------------------------------------- private static class MyEngineExecutionContext implements EngineExecutionContext { From 8b69a4aae972d159163514485eafced012af5e71 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 7 Jul 2025 15:33:03 +0000 Subject: [PATCH 044/162] Provide cancellation support for Suite engine (#4730) The Suite engine now passes the `CancellationToken` to downstream test engines and checks whether cancellation has been requested when about to execute a `@Suite` class. Issue: #4725 --- .../release-notes/release-notes-6.0.0-M2.adoc | 1 + .../advanced-topics/launcher-api.adoc | 3 +- .../platform/suite/engine/SuiteLauncher.java | 10 +-- .../suite/engine/SuiteTestDescriptor.java | 27 ++++-- .../suite/engine/SuiteTestEngine.java | 4 +- .../suite/engine/SuiteEngineTests.java | 87 +++++++++++++++++-- 6 files changed, 110 insertions(+), 22 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index f6f48e5bbea4..9b0b2d4a0daa 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -43,6 +43,7 @@ repository on GitHub. and a usage example. * Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as JUnit Jupiter, Spock, and Cucumber. +* Provide cancellation support for Suite engine * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc index 35c8fe642cf5..603a451a304f 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -384,8 +384,9 @@ Cancelling tests relies on <> checking and responding to the `Launcher` will also check the token and cancel test execution when multiple test engines are present at runtime. -At the time of writing the following test engines support cancellation: +At the time of writing, the following test engines support cancellation: * `{junit-jupiter-engine}` +* `{junit-platform-suite-engine}` * Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber ==== diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java index eb830c130507..f468c70ec799 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java @@ -59,13 +59,11 @@ LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, Uniq return discoveryOrchestrator.discover(discoveryRequest, parentId); } - TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, - EngineExecutionListener parentEngineExecutionListener, - NamespacedHierarchicalStore requestLevelStore) { + TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, EngineExecutionListener executionListener, + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken) { SummaryGeneratingListener listener = new SummaryGeneratingListener(); - // TODO #4725 Provide cancellation support for Suite engine - executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore, - CancellationToken.disabled()); + executionOrchestrator.execute(discoveryResult, executionListener, listener, requestLevelStore, + cancellationToken); return listener.getSummary(); } 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 356fe4e5ff17..0edf9c1eefbe 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 @@ -27,6 +27,7 @@ import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.EngineDiscoveryListener; @@ -148,20 +149,26 @@ private static String getSuiteDisplayName(Class testClass) { // @formatter:on } - void execute(EngineExecutionListener parentEngineExecutionListener, - NamespacedHierarchicalStore requestLevelStore) { - parentEngineExecutionListener.executionStarted(this); + void execute(EngineExecutionListener executionListener, NamespacedHierarchicalStore requestLevelStore, + CancellationToken cancellationToken) { + + if (cancellationToken.isCancellationRequested()) { + executionListener.executionSkipped(this, "Execution cancelled"); + return; + } + + executionListener.executionStarted(this); ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector(); executeBeforeSuiteMethods(throwableCollector); - TestExecutionSummary summary = executeTests(parentEngineExecutionListener, requestLevelStore, + TestExecutionSummary summary = executeTests(executionListener, requestLevelStore, cancellationToken, throwableCollector); executeAfterSuiteMethods(throwableCollector); TestExecutionResult testExecutionResult = computeTestExecutionResult(summary, throwableCollector); - parentEngineExecutionListener.executionFinished(this, testExecutionResult); + executionListener.executionFinished(this, testExecutionResult); } private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) { @@ -176,8 +183,10 @@ private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) { } } - private @Nullable TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener, - NamespacedHierarchicalStore requestLevelStore, ThrowableCollector throwableCollector) { + private @Nullable TestExecutionSummary executeTests(EngineExecutionListener executionListener, + NamespacedHierarchicalStore requestLevelStore, CancellationToken cancellationToken, + ThrowableCollector throwableCollector) { + if (throwableCollector.isNotEmpty()) { return null; } @@ -187,7 +196,9 @@ private void executeBeforeSuiteMethods(ThrowableCollector throwableCollector) { // be pruned accordingly. LauncherDiscoveryResult discoveryResult = requireNonNull(this.launcherDiscoveryResult).withRetainedEngines( getChildren()::contains); - return requireNonNull(launcher).execute(discoveryResult, parentEngineExecutionListener, requestLevelStore); + + return requireNonNull(launcher).execute(discoveryResult, executionListener, requestLevelStore, + cancellationToken); } private void executeAfterSuiteMethods(ThrowableCollector throwableCollector) { diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java index d75cf8c3dfbf..3f13da412285 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java @@ -15,6 +15,7 @@ import java.util.Optional; import org.apiguardian.api.API; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; @@ -66,6 +67,7 @@ public void execute(ExecutionRequest request) { SuiteEngineDescriptor suiteEngineDescriptor = (SuiteEngineDescriptor) request.getRootTestDescriptor(); EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); NamespacedHierarchicalStore requestLevelStore = request.getStore(); + CancellationToken cancellationToken = request.getCancellationToken(); engineExecutionListener.executionStarted(suiteEngineDescriptor); @@ -73,7 +75,7 @@ public void execute(ExecutionRequest request) { suiteEngineDescriptor.getChildren() .stream() .map(SuiteTestDescriptor.class::cast) - .forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore)); + .forEach(suiteTestDescriptor -> suiteTestDescriptor.execute(engineExecutionListener, requestLevelStore, cancellationToken)); // @formatter:on engineExecutionListener.executionFinished(suiteEngineDescriptor, TestExecutionResult.successful()); } diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java index 2050d1635f9d..12cb46e6d928 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java @@ -10,6 +10,7 @@ package org.junit.platform.suite.engine; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; @@ -22,6 +23,8 @@ import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.skippedWithReason; +import static org.junit.platform.testkit.engine.EventConditions.started; import static org.junit.platform.testkit.engine.EventConditions.test; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; @@ -32,6 +35,7 @@ import java.nio.file.Path; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; @@ -39,6 +43,7 @@ import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.DiscoveryIssue; import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.EngineExecutionListener; @@ -51,6 +56,8 @@ import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.core.NamespacedHierarchicalStoreProviders; +import org.junit.platform.suite.api.AfterSuite; +import org.junit.platform.suite.api.BeforeSuite; import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.ConfigurationSensitiveTestCase; @@ -628,11 +635,6 @@ void discoveryIssueOfNestedTestEnginesAreReported() throws Exception { // @formatter:on } - @Suite - @SelectClasses(SingleTestTestCase.class) - abstract private static class AbstractPrivateSuite { - } - @Test void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() { UniqueId engineId = UniqueId.forEngine(SuiteEngineDescriptor.ENGINE_ID); @@ -643,15 +645,88 @@ void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() { EngineExecutionListener listener = mock(EngineExecutionListener.class); NamespacedHierarchicalStore requestLevelStore = NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore(); + var cancellationToken = CancellationToken.create(); ExecutionRequest request = mock(); when(request.getRootTestDescriptor()).thenReturn(engineDescriptor); when(request.getEngineExecutionListener()).thenReturn(listener); when(request.getStore()).thenReturn(requestLevelStore); + when(request.getCancellationToken()).thenReturn(cancellationToken); new SuiteTestEngine().execute(request); - verify(mockDescriptor).execute(same(listener), same(requestLevelStore)); + verify(mockDescriptor).execute(same(listener), same(requestLevelStore), same(cancellationToken)); + } + + @Test + void reportsSuiteClassAsSkippedWhenCancelledBeforeExecution() { + CancellingSuite.cancellationToken = CancellationToken.create(); + try { + var testKit = EngineTestKit.engine(ENGINE_ID) // + .selectors(selectClass(CancellingSuite.class), selectClass(SelectMethodsSuite.class)) // + .cancellationToken(CancellingSuite.cancellationToken); + + var results = testKit.execute(); + + results.allEvents() // + .assertStatistics(stats -> stats.started(3).succeeded(2).aborted(1).skipped(2)) // + .assertEventsMatchLooselyInOrder( // + event(container(CancellingSuite.class), started()), // + event(container(SingleTestTestCase.class), skippedWithReason("Execution cancelled")), // + event(container(CancellingSuite.class), finishedSuccessfully()), // + event(container(SelectMethodsSuite.class), skippedWithReason("Execution cancelled")) // + ); + } + finally { + CancellingSuite.cancellationToken = null; + } + } + + @Test + void reportsChildrenOfEnginesInSuiteAsSkippedWhenCancelledDuringExecution() { + CancellingSuite.cancellationToken = CancellationToken.create(); + try { + var testKit = EngineTestKit.engine(ENGINE_ID) // + .selectors(selectClass(CancellingSuite.class)) // + .cancellationToken(CancellingSuite.cancellationToken); + + var results = testKit.execute(); + + results.allEvents().assertThatEvents() // + .haveExactly(1, event(container(SingleTestTestCase.class), + skippedWithReason("Execution cancelled"))).haveExactly(0, event(test(), started())); + + assertThat(CancellingSuite.afterCalled) // + .describedAs("@AfterSuite method was called") // + .isTrue(); + } + finally { + CancellingSuite.cancellationToken = null; + } + } + + // ----------------------------------------------------------------------------------------------------------------- + + static class CancellingSuite extends SelectClassesSuite { + + static @Nullable CancellationToken cancellationToken; + static boolean afterCalled; + + @BeforeSuite + static void beforeSuite() { + CancellingSuite.afterCalled = false; + requireNonNull(cancellationToken).cancel(); + } + + @AfterSuite + static void afterSuite() { + afterCalled = true; + } + } + + @Suite + @SelectClasses(SingleTestTestCase.class) + abstract private static class AbstractPrivateSuite { } @Suite From e97c15e90ff10199dc3db90bdaa9846a3ba7b7ab Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 7 Jul 2025 18:42:00 +0200 Subject: [PATCH 045/162] Remove 'Overview' heading to simplify creating PRs using the GitHub CLI --- .github/PULL_REQUEST_TEMPLATE.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cfeb6a90b3ee..36a9551af336 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,3 @@ -## Overview - --- From fe1beff3b41bb276f71f398c6d530fec352a2699 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 01:02:34 +0000 Subject: [PATCH 046/162] Update plugin spotless to v7.1.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6aa669dd0f17..40dc110e72db 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -109,4 +109,4 @@ nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } openrewrite = { id = "org.openrewrite.rewrite", version = "7.9.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } shadow = { id = "com.gradleup.shadow", version = "8.3.8" } -spotless = { id = "com.diffplug.spotless", version = "7.0.4" } +spotless = { id = "com.diffplug.spotless", version = "7.1.0" } From 6c33cd4c7edd62db2cbac3160f1b8b8eedc853ca Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 8 Jul 2025 05:35:45 +0000 Subject: [PATCH 047/162] Introduce `--fail-fast` mode for `execute` command of `ConsoleLauncher` (#4732) Passing the option to the `execute` subcommand of the `ConsoleLauncher` now causes test execution to be cancelled after the first failed test. Issue: #4725 --- .../release-notes/release-notes-6.0.0-M2.adoc | 2 + .../console/options/ExecuteTestsCommand.java | 14 ++++++- .../console/tasks/ConsoleTestExecutor.java | 40 ++++++++++++++----- .../console/tasks/FailFastListener.java | 37 +++++++++++++++++ .../ConsoleLauncherIntegrationTests.java | 13 ++++++ .../console/ConsoleLauncherWrapper.java | 2 +- .../options/ExecuteTestsCommandTests.java | 15 ++++++- .../console/subpackage/FailingTestCase.java | 28 +++++++++++++ .../tasks/ConsoleTestExecutorTests.java | 12 +++--- 9 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 junit-platform-console/src/main/java/org/junit/platform/console/tasks/FailFastListener.java create mode 100644 platform-tests/src/test/java/org/junit/platform/console/subpackage/FailingTestCase.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 9b0b2d4a0daa..866518e85a3c 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -41,6 +41,8 @@ repository on GitHub. all registered test engines. Please refer to the <<../user-guide/index.adoc#launcher-api-launcher-cancellation, User Guide>> for details and a usage example. +* Passing the `--fail-fast` option to the `execute` subcommand of the `ConsoleLauncher` + now causes test execution to be cancelled after the first failed test. * Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as JUnit Jupiter, Spock, and Cucumber. * Provide cancellation support for Suite engine 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 ca6c4d0281c8..417c968656c7 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 @@ -60,13 +60,17 @@ class ExecuteTestsCommand extends BaseCommand implements C @Override protected TestExecutionSummary execute(PrintWriter out) { return consoleTestExecutorFactory.create(toTestDiscoveryOptions(), toTestConsoleOutputOptions()) // - .execute(out, getReportsDir()); + .execute(out, getReportsDir(), isFailFast()); } Optional getReportsDir() { return getReportingOptions().flatMap(ReportingOptions::getReportsDir); } + boolean isFailFast() { + return getReportingOptions().map(options -> options.failFast).orElse(false); + } + private Optional getReportingOptions() { return Optional.ofNullable(reportingOptions); } @@ -96,7 +100,13 @@ public int getExitCode() { static class ReportingOptions { @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 + private boolean failIfNoTests; + + /** + * @since 6.0 + */ + @Option(names = "--fail-fast", description = "Stops test execution after the first failed test.") + private boolean failFast; @Nullable @Option(names = "--reports-dir", paramLabel = "DIR", description = "Enable report output into a specified local directory (will be created if it does not exist).") diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java index a030d65805e0..cf61a8e35cb4 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java @@ -10,6 +10,7 @@ package org.junit.platform.console.tasks; +import static java.util.Objects.requireNonNullElseGet; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.console.tasks.DiscoveryRequestCreator.toDiscoveryRequestBuilder; import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; @@ -25,17 +26,18 @@ import java.util.function.Supplier; import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.console.options.Details; import org.junit.platform.console.options.TestConsoleOutputOptions; import org.junit.platform.console.options.TestDiscoveryOptions; import org.junit.platform.console.options.Theme; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -83,9 +85,9 @@ public void discover(PrintWriter out) { }); } - public TestExecutionSummary execute(PrintWriter out, Optional reportsDir) { + public TestExecutionSummary execute(PrintWriter out, Optional reportsDir, boolean failFast) { return createCustomContextClassLoaderExecutor() // - .invoke(() -> executeTests(out, reportsDir)); + .invoke(() -> executeTests(out, reportsDir, failFast)); } private CustomContextClassLoaderExecutor createCustomContextClassLoaderExecutor() { @@ -115,16 +117,17 @@ private static void printFoundTestsSummary(PrintWriter out, TestPlan testPlan) { out.flush(); } - private TestExecutionSummary executeTests(PrintWriter out, Optional reportsDir) { + private TestExecutionSummary executeTests(PrintWriter out, Optional reportsDir, boolean failFast) { Launcher launcher = launcherSupplier.get(); - SummaryGeneratingListener summaryListener = registerListeners(out, reportsDir, launcher); + CancellationToken cancellationToken = failFast ? CancellationToken.create() : null; + SummaryGeneratingListener summaryListener = registerListeners(out, reportsDir, launcher, cancellationToken); PrintStream originalOut = System.out; PrintStream originalErr = System.err; try (StandardStreamsHandler standardStreamsHandler = new StandardStreamsHandler()) { standardStreamsHandler.redirectStandardStreams(outputOptions.getStdoutPath(), outputOptions.getStderrPath()); - launchTests(launcher, reportsDir); + launchTests(launcher, reportsDir, cancellationToken); } finally { System.setOut(originalOut); @@ -136,14 +139,24 @@ private TestExecutionSummary executeTests(PrintWriter out, Optional report printSummary(summary, out); } + if (cancellationToken != null && cancellationToken.isCancellationRequested()) { + out.println("Test execution was cancelled due to --fail-fast mode."); + out.println(); + } + return summary; } - private void launchTests(Launcher launcher, Optional reportsDir) { - LauncherDiscoveryRequestBuilder discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions); + private void launchTests(Launcher launcher, Optional reportsDir, + @Nullable CancellationToken cancellationToken) { + + var discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions); reportsDir.ifPresent(dir -> discoveryRequestBuilder.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, dir.toAbsolutePath().toString())); - launcher.execute(discoveryRequestBuilder.forExecution().build()); + var executionRequest = discoveryRequestBuilder.forExecution() // + .cancellationToken(requireNonNullElseGet(cancellationToken, CancellationToken::disabled)) // + .build(); + launcher.execute(executionRequest); } private Optional createCustomClassLoader() { @@ -166,7 +179,9 @@ private URL toURL(Path path) { } } - private SummaryGeneratingListener registerListeners(PrintWriter out, Optional reportsDir, Launcher launcher) { + private SummaryGeneratingListener registerListeners(PrintWriter out, Optional reportsDir, Launcher launcher, + @Nullable CancellationToken cancellationToken) { + // always register summary generating listener SummaryGeneratingListener summaryListener = new SummaryGeneratingListener(); launcher.registerTestExecutionListeners(summaryListener); @@ -174,6 +189,7 @@ private SummaryGeneratingListener registerListeners(PrintWriter out, Optional createXmlWritingListener(PrintWriter out return reportsDir.map(it -> new LegacyXmlReportGeneratingListener(it, out)); } + private Optional createFailFastListener(@Nullable CancellationToken cancellationToken) { + return Optional.ofNullable(cancellationToken).map(FailFastListener::new); + } + private void printSummary(TestExecutionSummary summary, PrintWriter out) { // Otherwise the failures have already been printed in detail if (EnumSet.of(Details.NONE, Details.SUMMARY, Details.TREE).contains(outputOptions.getDetails())) { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FailFastListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FailFastListener.java new file mode 100644 index 000000000000..a39aaa744bbd --- /dev/null +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FailFastListener.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.tasks; + +import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; + +import org.junit.platform.engine.CancellationToken; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; + +/** + * @since 6.0 + */ +class FailFastListener implements TestExecutionListener { + + private final CancellationToken cancellationToken; + + FailFastListener(CancellationToken cancellationToken) { + this.cancellationToken = cancellationToken; + } + + @Override + public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult testExecutionResult) { + if (testExecutionResult.getStatus() == FAILED) { + cancellationToken.cancel(); + } + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java index 6fe058ff2493..1fd3c940c302 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java @@ -29,6 +29,7 @@ import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.console.options.StdStreamTestCase; +import org.junit.platform.console.subpackage.FailingTestCase; /** * @since 1.0 @@ -123,4 +124,16 @@ void executeWithRedirectedStdStreamsToSameFile(@TempDir Path tempDir) throws IOE Files.size(outputFile), "Invalid file size."); } + @Test + void stopsAfterFirstFailingTest() { + var result = new ConsoleLauncherWrapper().execute(1, "execute", "-e", "junit-jupiter", "--select-class", + FailingTestCase.class.getName(), "--fail-fast", "--disable-ansi-colors"); + + assertThat(result.getTestsStartedCount()).isEqualTo(1); + assertThat(result.getTestsFailedCount()).isEqualTo(1); + assertThat(result.getTestsSkippedCount()).isEqualTo(1); + + assertThat(result.out).endsWith("%nTest execution was cancelled due to --fail-fast mode.%n%n".formatted()); + } + } diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java index 03fb3528f543..de6ae6dfd381 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java @@ -55,7 +55,7 @@ public ConsoleLauncherWrapperResult execute(Optional expectedCode, Stri if (expectedCode.isPresent()) { int expectedValue = expectedCode.get(); assertEquals(expectedValue, code, "ConsoleLauncher execute code mismatch!"); - if (expectedValue != 0) { + if (expectedValue != 0 && expectedValue != 1) { assertThat(errText).isNotBlank(); } } diff --git a/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java b/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java index f259409c39a6..e95d00ce9427 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java @@ -13,7 +13,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -36,7 +39,7 @@ class ExecuteTestsCommandTests { @BeforeEach void setUp() { - when(consoleTestExecutor.execute(any(), any())).thenReturn(summary); + when(consoleTestExecutor.execute(any(), any(), anyBoolean())).thenReturn(summary); } @Test @@ -106,6 +109,16 @@ void parseValidXmlReportsDirs() { // @formatter:on } + @Test + void parseValidFailFast() { + // @formatter:off + assertAll( + () -> assertFalse(parseArgs().isFailFast()), + () -> assertTrue(parseArgs("--fail-fast").isFailFast()) + ); + // @formatter:on + } + private ExecuteTestsCommand parseArgs(String... args) { command.parseArgs(args); return command; diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/FailingTestCase.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/FailingTestCase.java new file mode 100644 index 000000000000..923ee1762ea2 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/FailingTestCase.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.subpackage; + +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +public class FailingTestCase { + + @Test + void first() { + fail(); + } + + @Test + void second() { + fail(); + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java index 76da53bcc218..c31fb24e088b 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java @@ -53,7 +53,7 @@ void printsSummary() { dummyTestEngine.addTest("failingTest", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter), Optional.empty()); + task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("Test run finished after", "2 tests found", "0 tests skipped", "2 tests started", "0 tests aborted", "1 tests successful", "1 tests failed"); @@ -66,7 +66,7 @@ void printsDetailsIfTheyAreNotHidden() { dummyTestEngine.addTest("failingTest", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter), Optional.empty()); + task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("Test execution started."); } @@ -78,7 +78,7 @@ void printsNoDetailsIfTheyAreHidden() { dummyTestEngine.addTest("failingTest", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter), Optional.empty()); + task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).doesNotContain("Test execution started."); } @@ -91,7 +91,7 @@ void printsFailuresEvenIfDetailsAreHidden() { dummyTestEngine.addContainer("failingContainer", FAILING_BLOCK); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter), Optional.empty()); + task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("Failures (2)", "failingTest", "failingContainer"); } @@ -105,7 +105,7 @@ void usesCustomClassLoaderIfAdditionalClassPathEntriesArePresent() { () -> assertSame(oldClassLoader, getDefaultClassLoader(), "should fail")); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter), Optional.empty()); + task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); } @@ -119,7 +119,7 @@ void usesSameClassLoaderIfNoAdditionalClassPathEntriesArePresent() { () -> assertNotSame(oldClassLoader, getDefaultClassLoader(), "should fail")); var task = new ConsoleTestExecutor(discoveryOptions, outputOptions, () -> createLauncher(dummyTestEngine)); - task.execute(new PrintWriter(stringWriter), Optional.empty()); + task.execute(new PrintWriter(stringWriter), Optional.empty(), false); assertThat(stringWriter.toString()).contains("failingTest", "should fail", "1 tests failed"); } From 35d51ebf333748e38d1b2233c09be062ad085246 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 8 Jul 2025 09:06:12 +0000 Subject: [PATCH 048/162] Provide cancellation support for Vintage engine (#4735) Issue: #4725 --- .../release-notes/release-notes-6.0.0-M2.adoc | 2 +- .../advanced-topics/launcher-api.adoc | 1 + .../vintage/engine/VintageTestEngine.java | 3 +- .../descriptor/RunnerTestDescriptor.java | 4 ++ .../CancellationTokenAwareRunNotifier.java | 36 +++++++++++++++++ .../engine/execution/RunListenerAdapter.java | 4 ++ .../engine/execution/RunnerExecutor.java | 39 ++++++++++++++++--- .../vintage/engine/execution/TestRun.java | 6 +++ .../engine/execution/VintageExecutor.java | 35 ++++++++--------- .../VintageTestEngineExecutionTests.java | 36 +++++++++++++++++ .../samples/junit4/CancellingTestCase.java | 38 ++++++++++++++++++ 11 files changed, 178 insertions(+), 26 deletions(-) create mode 100644 junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/CancellationTokenAwareRunNotifier.java create mode 100644 junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CancellingTestCase.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 866518e85a3c..91ccc894b9d3 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -45,7 +45,7 @@ repository on GitHub. now causes test execution to be cancelled after the first failed test. * Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as JUnit Jupiter, Spock, and Cucumber. -* Provide cancellation support for Suite engine +* Provide cancellation support for the Suite and Vintage test engines * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc index 603a451a304f..69ebdacc2142 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -387,6 +387,7 @@ are present at runtime. At the time of writing, the following test engines support cancellation: * `{junit-jupiter-engine}` +* `{junit-vintage-engine}` * `{junit-platform-suite-engine}` * Any `{TestEngine}` extending `{HierarchicalTestEngine}` such as Spock and Cucumber ==== diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java index 5bd0ee01e6a4..f5b805705472 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java @@ -71,7 +71,8 @@ public void execute(ExecutionRequest request) { VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); // TODO #4725 Provide cancellation support for Vintage engine engineExecutionListener.executionStarted(engineDescriptor); - new VintageExecutor(engineDescriptor, engineExecutionListener, request).executeAllChildren(); + new VintageExecutor(engineDescriptor, engineExecutionListener, + request.getConfigurationParameters()).executeAllChildren(request.getCancellationToken()); engineExecutionListener.executionFinished(engineDescriptor, successful()); } } 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 2fa6af237698..2ebeb1a3a4da 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 @@ -74,6 +74,10 @@ public Request toRequest() { return new RunnerRequest(this.runner); } + public Runner getRunner() { + return runner; + } + @Override protected boolean tryToExcludeFromRunner(Description description) { boolean excluded = tryToFilterRunner(description); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/CancellationTokenAwareRunNotifier.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/CancellationTokenAwareRunNotifier.java new file mode 100644 index 000000000000..d3d01071144a --- /dev/null +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/CancellationTokenAwareRunNotifier.java @@ -0,0 +1,36 @@ +/* + * 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.execution; + +import org.junit.platform.engine.CancellationToken; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; +import org.junit.runner.notification.StoppedByUserException; + +/** + * @since 6.0 + */ +class CancellationTokenAwareRunNotifier extends RunNotifier { + + private final CancellationToken cancellationToken; + + CancellationTokenAwareRunNotifier(CancellationToken cancellationToken) { + this.cancellationToken = cancellationToken; + } + + @Override + public void fireTestStarted(Description description) throws StoppedByUserException { + if (cancellationToken.isCancellationRequested()) { + pleaseStop(); + } + super.fireTestStarted(description); + } +} 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 6a0b4756f7fd..6c48e48e81a4 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 @@ -103,6 +103,10 @@ public void testSuiteFinished(Description description) { @Override public void testRunFinished(Result result) { + testRunFinished(); + } + + void testRunFinished() { reportContainerFinished(testRun.getRunnerTestDescriptor()); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java index 7e5bf6b1306a..11bffb6f1c79 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java @@ -15,9 +15,11 @@ import org.apiguardian.api.API; import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestExecutionResult; -import org.junit.runner.JUnitCore; +import org.junit.runner.notification.RunNotifier; +import org.junit.runner.notification.StoppedByUserException; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; import org.junit.vintage.engine.descriptor.TestSourceProvider; @@ -28,18 +30,30 @@ public class RunnerExecutor { private final EngineExecutionListener engineExecutionListener; + private final CancellationToken cancellationToken; private final TestSourceProvider testSourceProvider = new TestSourceProvider(); - public RunnerExecutor(EngineExecutionListener engineExecutionListener) { + public RunnerExecutor(EngineExecutionListener engineExecutionListener, CancellationToken cancellationToken) { this.engineExecutionListener = engineExecutionListener; + this.cancellationToken = cancellationToken; } public void execute(RunnerTestDescriptor runnerTestDescriptor) { - TestRun testRun = new TestRun(runnerTestDescriptor); - JUnitCore core = new JUnitCore(); - core.addListener(new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider)); + if (cancellationToken.isCancellationRequested()) { + engineExecutionListener.executionSkipped(runnerTestDescriptor, "Execution cancelled"); + return; + } + RunNotifier notifier = new CancellationTokenAwareRunNotifier(cancellationToken); + var testRun = new TestRun(runnerTestDescriptor); + var listener = new RunListenerAdapter(testRun, engineExecutionListener, testSourceProvider); + notifier.addListener(listener); try { - core.run(runnerTestDescriptor.toRequest()); + listener.testRunStarted(runnerTestDescriptor.getDescription()); + runnerTestDescriptor.getRunner().run(notifier); + listener.testRunFinished(); + } + catch (StoppedByUserException e) { + reportEventsForCancellation(e, testRun); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); @@ -47,6 +61,19 @@ public void execute(RunnerTestDescriptor runnerTestDescriptor) { } } + private void reportEventsForCancellation(StoppedByUserException exception, TestRun testRun) { + testRun.getInProgressTestDescriptors().forEach(startedDescriptor -> { + startedDescriptor.getChildren().forEach(child -> { + if (!testRun.isFinishedOrSkipped(child)) { + engineExecutionListener.executionSkipped(child, "Execution cancelled"); + testRun.markSkipped(child); + } + }); + engineExecutionListener.executionFinished(startedDescriptor, TestExecutionResult.aborted(exception)); + testRun.markFinished(startedDescriptor); + }); + } + private void reportUnexpectedFailure(TestRun testRun, RunnerTestDescriptor runnerTestDescriptor, TestExecutionResult result) { if (testRun.isNotStarted(runnerTestDescriptor)) { 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 1b7814a8a106..c33c0afc7436 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 @@ -87,6 +87,12 @@ Collection getInProgressTestDescriptorsWithSyntheticStartEvents( return result; } + Collection getInProgressTestDescriptors() { + List result = new ArrayList<>(inProgressDescriptors.keySet()); + Collections.reverse(result); + return result; + } + boolean isDescendantOfRunnerTestDescriptor(TestDescriptor testDescriptor) { return runnerDescendants.contains(testDescriptor); } 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 450dc4fde4dd..e8c1cfef0dc4 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 @@ -27,8 +27,9 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ExceptionUtils; +import org.junit.platform.engine.CancellationToken; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.vintage.engine.Constants; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; @@ -48,56 +49,54 @@ public class VintageExecutor { private final VintageEngineDescriptor engineDescriptor; private final EngineExecutionListener engineExecutionListener; - private final ExecutionRequest request; + private final ConfigurationParameters configurationParameters; private final boolean parallelExecutionEnabled; private final boolean classes; private final boolean methods; public VintageExecutor(VintageEngineDescriptor engineDescriptor, EngineExecutionListener engineExecutionListener, - ExecutionRequest request) { + ConfigurationParameters configurationParameters) { this.engineDescriptor = engineDescriptor; this.engineExecutionListener = engineExecutionListener; - this.request = request; - this.parallelExecutionEnabled = request.getConfigurationParameters().getBoolean( - Constants.PARALLEL_EXECUTION_ENABLED).orElse(false); - this.classes = request.getConfigurationParameters().getBoolean(Constants.PARALLEL_CLASS_EXECUTION).orElse( - false); - this.methods = request.getConfigurationParameters().getBoolean(Constants.PARALLEL_METHOD_EXECUTION).orElse( + this.configurationParameters = configurationParameters; + this.parallelExecutionEnabled = configurationParameters.getBoolean(Constants.PARALLEL_EXECUTION_ENABLED).orElse( false); + this.classes = configurationParameters.getBoolean(Constants.PARALLEL_CLASS_EXECUTION).orElse(false); + this.methods = configurationParameters.getBoolean(Constants.PARALLEL_METHOD_EXECUTION).orElse(false); } - public void executeAllChildren() { + public void executeAllChildren(CancellationToken cancellationToken) { if (!parallelExecutionEnabled) { - executeClassesAndMethodsSequentially(); + executeClassesAndMethodsSequentially(cancellationToken); return; } if (!classes && !methods) { logger.warn(() -> "Parallel execution is enabled but no scope is defined. " + "Falling back to sequential execution."); - executeClassesAndMethodsSequentially(); + executeClassesAndMethodsSequentially(cancellationToken); return; } - boolean wasInterrupted = executeInParallel(); + boolean wasInterrupted = executeInParallel(cancellationToken); if (wasInterrupted) { Thread.currentThread().interrupt(); } } - private void executeClassesAndMethodsSequentially() { - RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); + private void executeClassesAndMethodsSequentially(CancellationToken cancellationToken) { + RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener, cancellationToken); for (Iterator iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) { runnerExecutor.execute((RunnerTestDescriptor) iterator.next()); iterator.remove(); } } - private boolean executeInParallel() { + private boolean executeInParallel(CancellationToken cancellationToken) { ExecutorService executorService = Executors.newWorkStealingPool(getThreadPoolSize()); - RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); + RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener, cancellationToken); List runnerTestDescriptors = collectRunnerTestDescriptors(executorService); @@ -110,7 +109,7 @@ private boolean executeInParallel() { } private int getThreadPoolSize() { - Optional optionalPoolSize = request.getConfigurationParameters().get(Constants.PARALLEL_POOL_SIZE); + Optional optionalPoolSize = configurationParameters.get(Constants.PARALLEL_POOL_SIZE); if (optionalPoolSize.isPresent()) { try { int poolSize = Integer.parseInt(optionalPoolSize.get()); diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index d8c7d4b75ca7..ef886c6c5699 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledInEclipse; import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -57,6 +58,7 @@ import org.junit.runner.RunWith; import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; +import org.junit.runner.notification.StoppedByUserException; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import org.junit.vintage.engine.samples.junit3.IgnoredJUnit3TestCase; @@ -64,6 +66,7 @@ import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites; import org.junit.vintage.engine.samples.junit3.JUnit4SuiteWithIgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; +import org.junit.vintage.engine.samples.junit4.CancellingTestCase; import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase; import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; import org.junit.vintage.engine.samples.junit4.EnclosedJUnit4TestCase; @@ -926,6 +929,32 @@ void executesJUnit4SuiteWithIgnoredJUnit3TestCase() { event(engine(), finishedSuccessfully())); } + @Test + void supportsCancellation() { + CancellingTestCase.cancellationToken = CancellationToken.create(); + try { + var results = vintageTestEngine() // + .selectors(selectClass(CancellingTestCase.class), + selectClass(PlainJUnit4TestCaseWithSingleTestWhichFails.class)) // + .cancellationToken(CancellingTestCase.cancellationToken) // + .execute(); + + results.allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(CancellingTestCase.class), started()), // + event(test(), started()), // + event(test(), finishedWithFailure()), // + event(test(), skippedWithReason("Execution cancelled")), // + event(container(CancellingTestCase.class), abortedWithReason(instanceOf(StoppedByUserException.class))), // + event(container(PlainJUnit4TestCaseWithSingleTestWhichFails.class), + skippedWithReason("Execution cancelled")), // + event(engine(), finishedSuccessfully())); + } + finally { + CancellingTestCase.cancellationToken = null; + } + } + private static EngineExecutionResults execute(Class testClass) { return execute(request(testClass)); } @@ -935,6 +964,12 @@ private static EngineExecutionResults execute(LauncherDiscoveryRequest request) return EngineTestKit.execute(new VintageTestEngine(), request); } + @SuppressWarnings("deprecation") + private static EngineTestKit.Builder vintageTestEngine() { + return EngineTestKit.engine(new VintageTestEngine()) // + .enableImplicitConfigurationParameters(false); + } + @SuppressWarnings("deprecation") private static void execute(Class testClass, EngineExecutionListener listener) { var testEngine = new VintageTestEngine(); @@ -943,6 +978,7 @@ private static void execute(Class testClass, EngineExecutionListener listener when(executionRequest.getRootTestDescriptor()).thenReturn(engineTestDescriptor); when(executionRequest.getEngineExecutionListener()).thenReturn(listener); when(executionRequest.getConfigurationParameters()).thenReturn(mock()); + when(executionRequest.getCancellationToken()).thenReturn(CancellationToken.disabled()); testEngine.execute(executionRequest); } diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CancellingTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CancellingTestCase.java new file mode 100644 index 000000000000..84ef344248a1 --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CancellingTestCase.java @@ -0,0 +1,38 @@ +/* + * 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.util.Objects.requireNonNull; +import static org.junit.Assert.fail; + +import org.junit.Before; +import org.junit.Test; +import org.junit.platform.engine.CancellationToken; + +public class CancellingTestCase { + + public static CancellationToken cancellationToken; + + @Before + public void cancelExecution() { + requireNonNull(cancellationToken).cancel(); + } + + @Test + public void first() { + fail(); + } + + @Test + public void second() { + fail(); + } +} From 7ea357d08d577c235a311f1debf89ca194c7fc33 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 9 Jul 2025 10:57:45 +0200 Subject: [PATCH 049/162] Add `@Nullable` --- .../jupiter/params/ParameterizedInvocationNameFormatter.java | 1 + 1 file changed, 1 insertion(+) 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 b1b61a4c5399..4df551843e4d 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 @@ -247,6 +247,7 @@ public synchronized void append(ArgumentsContext context, StringBuffer result) { } private @Nullable Object[] makeReadable(@Nullable Object[] arguments) { + @Nullable Format[] formats = messageFormat.getFormatsByArgumentIndex(); @Nullable Object[] result = Arrays.copyOf(arguments, Math.min(arguments.length, formats.length), Object[].class); From e973ec1a2f9935ad83f73ddac31a7d1c9f335d6d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 9 Jul 2025 11:16:17 +0200 Subject: [PATCH 050/162] Create initial 5.13.4 release notes from template --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes/release-notes-5.13.4.adoc | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 499fe4b4ffb8..1d5ceb6cabee 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -21,6 +21,8 @@ include::{basedir}/release-notes-6.0.0-M2.adoc[] include::{basedir}/release-notes-6.0.0-M1.adoc[] +include::{basedir}/release-notes-5.13.4.adoc[] + include::{basedir}/release-notes-5.13.3.adoc[] include::{basedir}/release-notes-5.13.2.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc new file mode 100644 index 000000000000..1480ac637377 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -0,0 +1,67 @@ +[[release-notes-5.13.4]] +== 5.13.4 + +*Date of Release:* ❓ + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit-framework-repo}+/milestone/101?closed=1+[5.13.4] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-5.13.4-junit-platform]] +=== JUnit Platform + +[[release-notes-5.13.4-junit-platform-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.13.4-junit-platform-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.13.4-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.13.4-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.13.4-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.13.4-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.13.4-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.13.4-junit-vintage]] +=== JUnit Vintage + +[[release-notes-5.13.4-junit-vintage-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.13.4-junit-vintage-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.13.4-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ From f045ef651ceb972dbace64ca0931e50f490d238c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 9 Jul 2025 15:41:12 +0000 Subject: [PATCH 051/162] Remove `java.*` packages from `Import-Package` headers in all jars (#4738) ... to maximize compatibility with older OSGi runtimes. Resolves #4733. --- .../asciidoc/release-notes/release-notes-5.13.4.adoc | 10 ++++++++++ .../main/kotlin/junitbuild.osgi-conventions.gradle.kts | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc index 1480ac637377..25dd429044a1 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -10,6 +10,16 @@ link:{junit-framework-repo}+/milestone/101?closed=1+[5.13.4] milestone page in t repository on GitHub. +[[release-notes-5.13.4-overall-improvements]] +=== Overall Changes + +[[release-notes-5.13.4-overall-new-features-and-improvements]] +==== New Features and Improvements + +* Remove `java.*` packages from `Import-Package` headers in all jar manifests to maximize + compatibility with older OSGi runtimes. + + [[release-notes-5.13.4-junit-platform]] === JUnit Platform 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 d5f47a50e2e8..68a7780d2314 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 @@ -67,6 +67,11 @@ tasks.withType().named { # Instruct the APIGuardianAnnotations how to operate. # See https://bnd.bndtools.org/instructions/export-apiguardian.html -export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}} + + # Avoid including java packages in Import-Package header to maximize compatibility with older OSGi runtimes. + # See https://bnd.bndtools.org/instructions/noimportjava.html + # Issue: https://github.com/junit-team/junit-framework/issues/4733 + -noimportjava: true """ ) From 40b296f793d8ab16cd9209a0b36bdae8b587696a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 9 Jul 2025 15:44:49 +0000 Subject: [PATCH 052/162] Require GraalVM as vendor for JDK used in `GraalVmStarterTests` (#4743) --- .../platform-tooling-support-tests.gradle.kts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 d2a769977d0c..c741388b6db6 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -2,6 +2,7 @@ import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigur import junitbuild.extensions.capitalized import junitbuild.extensions.dependencyProject import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.jvm.toolchain.JvmVendorSpec.GRAAL_VM import org.gradle.kotlin.dsl.support.listFilesOrdered import java.time.Duration @@ -227,7 +228,7 @@ val test by testing.suites.getting(JvmTestSuite::class) { val gradleJavaVersion = 21 jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled) - jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled, nativeImage = true) + jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled, graalvm = true) systemProperty("gradle.java.version", gradleJavaVersion) } } @@ -254,7 +255,7 @@ class MavenRepo(project: Project, @get:Internal val repoDir: Provider) : C override fun asArguments() = listOf("-Dmaven.repo=${repoDir.get().absolutePath}") } -class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEnabled: Provider, @Input val nativeImage: Boolean = false) : CommandLineArgumentProvider { +class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEnabled: Provider, @Input val graalvm: Boolean = false) : CommandLineArgumentProvider { @Internal val javaLauncher: Property = project.objects.property() @@ -262,7 +263,10 @@ class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEna try { project.javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(version) - nativeImageCapable = nativeImage + if (graalvm) { + vendor = GRAAL_VM + nativeImageCapable = true + } }.get() } catch (e: Exception) { null @@ -278,7 +282,7 @@ class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEna } val metadata = javaLauncher.map { it.metadata } val javaHome = metadata.map { it.installationPath.asFile.absolutePath }.orNull - return javaHome?.let { listOf("-Djava.home.$version${if (nativeImage) ".nativeImage" else ""}=$it") } ?: emptyList() + return javaHome?.let { listOf("-Djava.home.$version${if (graalvm) ".nativeImage" else ""}=$it") } ?: emptyList() } } From 0807b2c40ca76c9b1993735a6956767dfc7e2a34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:45:40 +0000 Subject: [PATCH 053/162] Update plugin openrewrite to v7.10.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 40dc110e72db..8855a5cf5a0b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -106,7 +106,7 @@ jreleaser = { id = "org.jreleaser", version = "1.19.0" } # check if workaround in gradle.properties can be removed when updating kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } -openrewrite = { id = "org.openrewrite.rewrite", version = "7.9.0" } +openrewrite = { id = "org.openrewrite.rewrite", version = "7.10.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } shadow = { id = "com.gradleup.shadow", version = "8.3.8" } spotless = { id = "com.diffplug.spotless", version = "7.1.0" } From 27e3e00919a1caab76a7c2aa7e802bd2ed3ff621 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:35:05 +0000 Subject: [PATCH 054/162] Update dependency org.openrewrite.recipe:rewrite-recipe-bom to v3.11.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8855a5cf5a0b..a9f345e44f83 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,7 @@ 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" } -openrewrite-recipe-bom = { module = "org.openrewrite.recipe:rewrite-recipe-bom", version = "3.10.1" } +openrewrite-recipe-bom = { module = "org.openrewrite.recipe:rewrite-recipe-bom", version = "3.11.1" } 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" } From 4e4dcad555a4b586e0bd02a81f131a92e545c26e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 23:35:00 +0000 Subject: [PATCH 055/162] Update dependency com.google.errorprone:error_prone_core to v2.40.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a9f345e44f83..00173a49c9ff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,7 +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.180" } commons-io = { module = "commons-io:commons-io", version = "2.19.0" } -errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.39.0" } +errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.40.0" } fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.27" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } From 4cd188e1cab597bf0cddcb6c425985398b69625f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 09:53:28 +0200 Subject: [PATCH 056/162] Make constant `static` --- .../jupiter/engine/config/DefaultJupiterConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 99f08e00ee4a..3b0ba96093b2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -48,7 +48,7 @@ @API(status = INTERNAL, since = "5.4") public class DefaultJupiterConfiguration implements JupiterConfiguration { - private final List UNSUPPORTED_CONFIGURATION_PARAMETERS = List.of("junit.jupiter.tempdir.scope"); + private static final List UNSUPPORTED_CONFIGURATION_PARAMETERS = List.of("junit.jupiter.tempdir.scope"); private static final EnumConfigurationParameterConverter executionModeConverter = // new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); From cfd5845d3d658b9d825caba09e1cb636c3f9fc6c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 12:01:34 +0200 Subject: [PATCH 057/162] Remove OpenRewrite from build We used OpenRewrite as a migration aid when bumping the Java baseline from 8 to 17. After evaluating its use for ongoing static analysis, it doesn't provide enough added value to justify maintaining configuration etc. for another tool. In #4748, additional Checkstyle rules will be enabled instead which address almost all issues found by OpenRewrite's recipes. Moreover, openrewrite/rewrite-gradle-plugin#212 prevents us from using it in our build. --- gradle/libs.versions.toml | 2 -- gradle/plugins/common/build.gradle.kts | 1 - .../junitbuild.java-library-conventions.gradle.kts | 11 ----------- 3 files changed, 14 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 00173a49c9ff..8ead394f36f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,6 @@ 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" } -openrewrite-recipe-bom = { module = "org.openrewrite.recipe:rewrite-recipe-bom", version = "3.11.1" } 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" } @@ -106,7 +105,6 @@ jreleaser = { id = "org.jreleaser", version = "1.19.0" } # check if workaround in gradle.properties can be removed when updating kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } -openrewrite = { id = "org.openrewrite.rewrite", version = "7.10.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } shadow = { id = "com.gradleup.shadow", version = "8.3.8" } spotless = { id = "com.diffplug.spotless", version = "7.1.0" } diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts index 40df3d0c9ce4..4b4ed22250a6 100644 --- a/gradle/plugins/common/build.gradle.kts +++ b/gradle/plugins/common/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { implementation(libs.plugins.foojayResolver.markerCoordinates) implementation(libs.plugins.jmh.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 a505a3c5b6e8..4b5960dafc3a 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,4 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import junitbuild.extensions.dependencyFromLibs import junitbuild.extensions.isSnapshot import org.gradle.plugins.ide.eclipse.model.Classpath import org.gradle.plugins.ide.eclipse.model.Library @@ -14,16 +13,6 @@ plugins { id("junitbuild.build-parameters") id("junitbuild.checkstyle-conventions") id("junitbuild.jacoco-java-conventions") - id("org.openrewrite.rewrite") -} - -rewrite { - activeRecipe("org.openrewrite.java.migrate.UpgradeToJava17") -} - -dependencies { - rewrite(platform(dependencyFromLibs("openrewrite-recipe-bom"))) - rewrite("org.openrewrite.recipe:rewrite-migrate-java") } val mavenizedProjects: List by rootProject.extra From 9236ce04da0beda4d6c3189e9f52952bd7f63d30 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 10:16:12 +0000 Subject: [PATCH 058/162] Enable ErrorProne checks and fix resulting findings (#4744) * Classes with equals calling `getClass()` may potentially problematic. I made them `final` where they were "effectively" `final` by only having private or package-private constructors. * I switched from `LinkedList` to `ArrayList` or `ArrayDeque` based on ErrorProne's recommendations. --- .../src/main/java/example/domain/Person.java | 2 +- ...ld.java-nullability-conventions.gradle.kts | 31 ++- .../org/junit/jupiter/api/Assertions.java | 198 +++++++++--------- .../org/junit/jupiter/api/Assumptions.java | 3 + .../org/junit/jupiter/api/condition/OS.java | 4 +- .../api/extension/ExtensionContext.java | 2 +- .../jupiter/api/extension/MediaType.java | 2 +- .../junit/jupiter/api/condition/JRE.java.jte | 20 +- .../descriptor/NestedClassTestDescriptor.java | 5 +- .../InterceptingExecutableInvoker.java | 4 +- .../engine/extension/TempDirectory.java | 1 + .../extension/TestInfoParameterResolver.java | 2 +- .../extension/TimeoutConfiguration.java | 3 +- .../engine/extension/TimeoutDuration.java | 3 +- .../extension/TimeoutInvocationFactory.java | 2 +- .../rules/ExpectedExceptionSupport.java | 6 +- .../params/ParameterizedClassContext.java | 14 +- .../ParameterizedInvocationNameFormatter.java | 2 + .../junit/jupiter/params/ResolverFacade.java | 8 +- .../params/provider/CsvArgumentsProvider.java | 1 + .../junit/platform/commons/function/Try.java | 4 +- .../commons/logging/LogRecordListener.java | 4 +- .../commons/support/AnnotationSupport.java | 3 + .../scanning/DefaultClasspathScanner.java | 6 +- .../commons/util/AnnotationUtils.java | 2 +- .../platform/commons/util/ExceptionUtils.java | 2 +- .../commons/util/ReflectionUtils.java | 11 +- .../commons/test/ConcurrencyTestingUtils.java | 1 + .../console/options/ConsoleUtils.java | 1 + .../platform/console/options/Details.java | 4 +- .../console/options/SelectorConverter.java | 2 + .../junit/platform/console/options/Theme.java | 3 +- .../platform/console/tasks/ColorPalette.java | 8 +- .../junit/platform/engine/FilterResult.java | 4 +- .../junit/platform/engine/TestDescriptor.java | 6 +- .../org/junit/platform/engine/UniqueId.java | 4 +- .../engine/discovery/ClassSelector.java | 2 +- .../discovery/ClasspathResourceSelector.java | 2 +- .../discovery/ClasspathRootSelector.java | 2 +- .../engine/discovery/DirectorySelector.java | 2 +- .../engine/discovery/FilePosition.java | 2 +- .../engine/discovery/FileSelector.java | 2 +- .../engine/discovery/IterationSelector.java | 2 +- .../engine/discovery/MethodSelector.java | 2 +- .../engine/discovery/ModuleSelector.java | 2 +- .../engine/discovery/NestedClassSelector.java | 2 +- .../discovery/NestedMethodSelector.java | 2 +- .../engine/discovery/PackageSelector.java | 2 +- .../engine/discovery/UniqueIdSelector.java | 2 +- .../engine/discovery/UriSelector.java | 2 +- .../platform/engine/reporting/FileEntry.java | 3 +- .../engine/reporting/ReportEntry.java | 3 +- .../descriptor/AbstractTestDescriptor.java | 1 + .../support/descriptor/ClassSource.java | 2 +- .../descriptor/ClasspathResourceSource.java | 2 +- .../descriptor/CompositeTestSource.java | 2 +- .../support/descriptor/DirectorySource.java | 2 +- .../support/descriptor/FilePosition.java | 2 +- .../engine/support/descriptor/FileSource.java | 2 +- .../support/descriptor/MethodSource.java | 2 +- .../support/descriptor/PackageSource.java | 2 +- .../discovery/DiscoveryIssueReporter.java | 1 + .../hierarchical/ExclusiveResource.java | 1 + ...inPoolHierarchicalTestExecutorService.java | 7 +- .../support/hierarchical/NodeTestTask.java | 1 + .../engine/support/store/Namespace.java | 2 +- .../platform/launcher/TestIdentifier.java | 1 + .../core/EngineDiscoveryOrchestrator.java | 6 +- .../core/LauncherConfigurationParameters.java | 87 +++++--- .../launcher/core/StreamInterceptor.java | 3 +- .../launcher/listeners/OutputDir.java | 27 ++- ...ortOnFailureLauncherDiscoveryListener.java | 2 +- .../LoggingLauncherDiscoveryListener.java | 2 +- .../launcher/tagexpression/ShuntingYard.java | 2 +- .../launcher/tagexpression/Tokenizer.java | 5 +- .../LegacyXmlReportGeneratingListener.java | 3 +- .../xml/OpenTestReportGeneratingListener.java | 1 + .../junit/platform/testkit/engine/Events.java | 1 + .../platform/testkit/engine/Executions.java | 2 + .../vintage/engine/execution/TestRun.java | 1 + .../tests/process/WatchedProcess.java | 1 + .../platform-tooling-support-tests.gradle.kts | 7 + 82 files changed, 353 insertions(+), 239 deletions(-) diff --git a/documentation/src/main/java/example/domain/Person.java b/documentation/src/main/java/example/domain/Person.java index 5da8625c722e..ad7d9ca52f69 100644 --- a/documentation/src/main/java/example/domain/Person.java +++ b/documentation/src/main/java/example/domain/Person.java @@ -12,7 +12,7 @@ import java.time.LocalDate; -public class Person { +public final class Person { public enum Gender { F, M diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts index a06ab1becca9..ddb8f9f67b40 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -19,9 +19,36 @@ nullaway { tasks.withType().configureEach { options.errorprone { - disableAllChecks = true + val onJ9 = java.toolchain.implementation.orNull == JvmImplementation.J9 + if (name == "compileJava" && !onJ9) { + disable( + + // This check is opinionated wrt. which method names it considers unsuitable for import which includes + // a few of our own methods in `ReflectionUtils` etc. + "BadImport", + + // The findings of this check are subjective because a named constant can be more readable in many cases + "UnnecessaryLambda", + + // Resolving findings for these checks requires ErrorProne's annotations which we don't want to use + "AnnotateFormatMethod", + "DoNotCallSuggester", + "InlineMeSuggester", + "ImmutableEnumChecker", + + // Resolving findings for this checks requires using Guava which we don't want to use + "StringSplitter", + + // Produces a lot of findings that we consider to be false positives, for example for package-private + // classes and methods + "MissingSummary", + ) + error("PackageLocation") + } else { + disableAllChecks = true + } nullaway { - if (java.toolchain.implementation.orNull == JvmImplementation.J9) { + if (onJ9) { disable() } else { enable() 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 90f5837520af..cc928e0eade9 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 @@ -116,7 +116,7 @@ protected Assertions() { *

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

    See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ - @SuppressWarnings("NullAway") + @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) 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. @@ -161,7 +161,7 @@ public static V fail(@Nullable String message, @Nullable Throwable cause) { *

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

    See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ - @SuppressWarnings("NullAway") + @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail(Supplier<@Nullable String> messageSupplier) { AssertionUtils.fail(messageSupplier); return null; // appeasing the compiler: this line will never be executed. @@ -1741,7 +1741,7 @@ public static void assertLinesMatch(Stream expectedLines, Stream // --- assertNotEquals ----------------------------------------------------- /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1751,7 +1751,7 @@ public static void assertNotEquals(byte unexpected, byte actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1761,7 +1761,7 @@ public static void assertNotEquals(byte unexpected, @Nullable Byte actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1771,7 +1771,7 @@ public static void assertNotEquals(@Nullable Byte unexpected, byte actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1781,7 +1781,7 @@ public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte act } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1793,7 +1793,7 @@ public static void assertNotEquals(byte unexpected, byte actual, @Nullable Strin } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1805,7 +1805,7 @@ public static void assertNotEquals(byte unexpected, @Nullable Byte actual, @Null } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1817,7 +1817,7 @@ public static void assertNotEquals(@Nullable Byte unexpected, byte actual, @Null } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1829,7 +1829,7 @@ public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte act } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -1842,7 +1842,7 @@ public static void assertNotEquals(byte unexpected, byte actual, Supplier<@Nulla } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -1856,7 +1856,7 @@ public static void assertNotEquals(byte unexpected, @Nullable Byte actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -1870,7 +1870,7 @@ public static void assertNotEquals(@Nullable Byte unexpected, byte actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -1884,7 +1884,7 @@ public static void assertNotEquals(@Nullable Byte unexpected, @Nullable Byte act } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1894,7 +1894,7 @@ public static void assertNotEquals(short unexpected, short actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1904,7 +1904,7 @@ public static void assertNotEquals(short unexpected, @Nullable Short actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1914,7 +1914,7 @@ public static void assertNotEquals(@Nullable Short unexpected, short actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -1924,7 +1924,7 @@ public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short a } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1936,7 +1936,7 @@ public static void assertNotEquals(short unexpected, short actual, @Nullable Str } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1948,7 +1948,7 @@ public static void assertNotEquals(short unexpected, @Nullable Short actual, @Nu } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1960,7 +1960,7 @@ public static void assertNotEquals(@Nullable Short unexpected, short actual, @Nu } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -1972,7 +1972,7 @@ public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short a } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -1985,7 +1985,7 @@ public static void assertNotEquals(short unexpected, short actual, Supplier<@Nul } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -1999,7 +1999,7 @@ public static void assertNotEquals(short unexpected, @Nullable Short actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2013,7 +2013,7 @@ public static void assertNotEquals(@Nullable Short unexpected, short actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2027,7 +2027,7 @@ public static void assertNotEquals(@Nullable Short unexpected, @Nullable Short a } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2037,7 +2037,7 @@ public static void assertNotEquals(int unexpected, int actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2047,7 +2047,7 @@ public static void assertNotEquals(int unexpected, @Nullable Integer actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2057,7 +2057,7 @@ public static void assertNotEquals(@Nullable Integer unexpected, int actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2067,7 +2067,7 @@ public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integ } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2079,7 +2079,7 @@ public static void assertNotEquals(int unexpected, int actual, @Nullable String } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2091,7 +2091,7 @@ public static void assertNotEquals(int unexpected, @Nullable Integer actual, @Nu } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2103,7 +2103,7 @@ public static void assertNotEquals(@Nullable Integer unexpected, int actual, @Nu } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2116,7 +2116,7 @@ public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integ } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2129,7 +2129,7 @@ public static void assertNotEquals(int unexpected, int actual, Supplier<@Nullabl } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2143,7 +2143,7 @@ public static void assertNotEquals(int unexpected, @Nullable Integer actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2157,7 +2157,7 @@ public static void assertNotEquals(@Nullable Integer unexpected, int actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2171,7 +2171,7 @@ public static void assertNotEquals(@Nullable Integer unexpected, @Nullable Integ } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2181,7 +2181,7 @@ public static void assertNotEquals(long unexpected, long actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2191,7 +2191,7 @@ public static void assertNotEquals(long unexpected, @Nullable Long actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2201,7 +2201,7 @@ public static void assertNotEquals(@Nullable Long unexpected, long actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2211,7 +2211,7 @@ public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long act } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2223,7 +2223,7 @@ public static void assertNotEquals(long unexpected, long actual, @Nullable Strin } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2235,7 +2235,7 @@ public static void assertNotEquals(long unexpected, @Nullable Long actual, @Null } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2247,7 +2247,7 @@ public static void assertNotEquals(@Nullable Long unexpected, long actual, @Null } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2259,7 +2259,7 @@ public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long act } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2272,7 +2272,7 @@ public static void assertNotEquals(long unexpected, long actual, Supplier<@Nulla } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2286,7 +2286,7 @@ public static void assertNotEquals(long unexpected, @Nullable Long actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2300,7 +2300,7 @@ public static void assertNotEquals(@Nullable Long unexpected, long actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2314,7 +2314,7 @@ public static void assertNotEquals(@Nullable Long unexpected, @Nullable Long act } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2327,7 +2327,7 @@ public static void assertNotEquals(float unexpected, float actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2340,7 +2340,7 @@ public static void assertNotEquals(float unexpected, @Nullable Float actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2353,7 +2353,7 @@ public static void assertNotEquals(@Nullable Float unexpected, float actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2366,7 +2366,7 @@ public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float a } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2381,7 +2381,7 @@ public static void assertNotEquals(float unexpected, float actual, @Nullable Str } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2396,7 +2396,7 @@ public static void assertNotEquals(float unexpected, @Nullable Float actual, @Nu } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2411,7 +2411,7 @@ public static void assertNotEquals(@Nullable Float unexpected, float actual, @Nu } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2426,7 +2426,7 @@ public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float a } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2439,7 +2439,7 @@ public static void assertNotEquals(float unexpected, float actual, Supplier<@Nul } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2453,7 +2453,7 @@ public static void assertNotEquals(float unexpected, @Nullable Float actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2467,7 +2467,7 @@ public static void assertNotEquals(@Nullable Float unexpected, float actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Float#equals(Object)} and {@link Float#compare(float, float)}. @@ -2481,7 +2481,7 @@ public static void assertNotEquals(@Nullable Float unexpected, @Nullable Float a } /** - * Assert that {@code expected} and {@code actual} are not equal + * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

    Inequality imposed by this method is consistent with @@ -2495,7 +2495,7 @@ public static void assertNotEquals(float unexpected, float actual, float delta) } /** - * Assert that {@code expected} and {@code actual} are not equal + * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

    Inequality imposed by this method is consistent with @@ -2511,7 +2511,7 @@ public static void assertNotEquals(float unexpected, float actual, float delta, } /** - * Assert that {@code expected} and {@code actual} are not equal + * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

    Inequality imposed by this method is consistent with @@ -2526,7 +2526,7 @@ public static void assertNotEquals(float unexpected, float actual, float delta, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2539,7 +2539,7 @@ public static void assertNotEquals(double unexpected, double actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2552,7 +2552,7 @@ public static void assertNotEquals(double unexpected, @Nullable Double actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2565,7 +2565,7 @@ public static void assertNotEquals(@Nullable Double unexpected, double actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2578,7 +2578,7 @@ public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2593,7 +2593,7 @@ public static void assertNotEquals(double unexpected, double actual, @Nullable S } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2608,7 +2608,7 @@ public static void assertNotEquals(double unexpected, @Nullable Double actual, @ } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2623,7 +2623,7 @@ public static void assertNotEquals(@Nullable Double unexpected, double actual, @ } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2638,7 +2638,7 @@ public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2651,7 +2651,7 @@ public static void assertNotEquals(double unexpected, double actual, Supplier<@N } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2665,7 +2665,7 @@ public static void assertNotEquals(double unexpected, @Nullable Double actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2679,7 +2679,7 @@ public static void assertNotEquals(@Nullable Double unexpected, double actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Inequality imposed by this method is consistent with * {@link Double#equals(Object)} and {@link Double#compare(double, double)}. @@ -2693,7 +2693,7 @@ public static void assertNotEquals(@Nullable Double unexpected, @Nullable Double } /** - * Assert that {@code expected} and {@code actual} are not equal + * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

    Inequality imposed by this method is consistent with @@ -2707,7 +2707,7 @@ public static void assertNotEquals(double unexpected, double actual, double delt } /** - * Assert that {@code expected} and {@code actual} are not equal + * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

    Inequality imposed by this method is consistent with @@ -2723,7 +2723,7 @@ public static void assertNotEquals(double unexpected, double actual, double delt } /** - * Assert that {@code expected} and {@code actual} are not equal + * Assert that {@code unexpected} and {@code actual} are not equal * within the given {@code delta}. * *

    Inequality imposed by this method is consistent with @@ -2738,7 +2738,7 @@ public static void assertNotEquals(double unexpected, double actual, double delt } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2748,7 +2748,7 @@ public static void assertNotEquals(char unexpected, char actual) { } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2758,7 +2758,7 @@ public static void assertNotEquals(char unexpected, @Nullable Character actual) } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2768,7 +2768,7 @@ public static void assertNotEquals(@Nullable Character unexpected, char actual) } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * * @since 5.4 */ @@ -2778,7 +2778,7 @@ public static void assertNotEquals(@Nullable Character unexpected, @Nullable Cha } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2790,7 +2790,7 @@ public static void assertNotEquals(char unexpected, char actual, @Nullable Strin } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2802,7 +2802,7 @@ public static void assertNotEquals(char unexpected, @Nullable Character actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2814,7 +2814,7 @@ public static void assertNotEquals(@Nullable Character unexpected, char actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails with the supplied failure {@code message}. * @@ -2827,7 +2827,7 @@ public static void assertNotEquals(@Nullable Character unexpected, @Nullable Cha } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2840,7 +2840,7 @@ public static void assertNotEquals(char unexpected, char actual, Supplier<@Nulla } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2854,7 +2854,7 @@ public static void assertNotEquals(char unexpected, @Nullable Character actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2868,7 +2868,7 @@ public static void assertNotEquals(@Nullable Character unexpected, char actual, } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -2882,7 +2882,7 @@ public static void assertNotEquals(@Nullable Character unexpected, @Nullable Cha } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails if both are {@code null}. * @@ -2893,7 +2893,7 @@ public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails if both are {@code null}. * @@ -2906,7 +2906,7 @@ public static void assertNotEquals(@Nullable Object unexpected, @Nullable Object } /** - * Assert that {@code expected} and {@code actual} are not equal. + * Assert that {@code unexpected} and {@code actual} are not equal. * *

    Fails if both are {@code null}. * @@ -3098,7 +3098,7 @@ public static void assertAll(Stream executables) throws MultipleFail * and all exceptions will be aggregated and reported in a {@link MultipleFailuresError}. * In addition, all aggregated exceptions will be added as {@linkplain * Throwable#addSuppressed(Throwable) suppressed exceptions} to the - * {@code MultipleFailuresError}. However, if an {@code executable} throws an + * {@code MultipleFailuresError}. However, if one of the {@code executables} throws an * unrecoverable exception — for example, an {@link OutOfMemoryError} * — execution will halt immediately, and the unrecoverable exception will be * rethrown as is but masked as an unchecked exception. 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 b0619ad3cc32..4e293a9fee4b 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 @@ -278,6 +278,7 @@ public static void assumingThat(boolean assumption, Executable executable) { * @since 5.9 */ @API(status = STABLE, since = "5.9") + @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort() { throw new TestAbortedException(); } @@ -301,6 +302,7 @@ public static V abort() { * @since 5.9 */ @API(status = STABLE, since = "5.9") + @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(String message) { throw new TestAbortedException(message); } @@ -317,6 +319,7 @@ public static V abort(String message) { * @since 5.9 */ @API(status = STABLE, since = "5.9") + @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(Supplier messageSupplier) { throw new TestAbortedException(messageSupplier.get()); } 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 0c7ab8be1bc6..9af0daed730d 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 @@ -147,8 +147,8 @@ public enum OS { } /** - * @return {@code true} if this {@code OS} is known to be the - * operating system on which the current JVM is executing + * {@return {@code true} if this {@code OS} is known to be the + * operating system on which the current JVM is executing} */ public boolean isCurrentOs() { return this == CURRENT_OS; 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 ce7523be8980..086a89f8d37d 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 @@ -751,7 +751,7 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * mixing data between extensions or across different invocations within the * lifecycle of a single extension. */ - class Namespace { + final class Namespace { /** * The default, global namespace which allows access to stored data from diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java index 2a8458e61a4e..83e9140af658 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java @@ -35,7 +35,7 @@ * @see ExtensionContext#publishFile(String, MediaType, ThrowingConsumer) */ @API(status = MAINTAINED, since = "5.13.3") -public class MediaType { +public final class MediaType { private static final Pattern PATTERN; static { diff --git a/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte b/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte index 4d7d64286bd8..8b7da5172543 100644 --- a/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte +++ b/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte @@ -113,9 +113,9 @@ public enum JRE { } /** - * @return {@code true} if this {@code JRE} is known to be the + * {@return {@code true} if this {@code JRE} is known to be the * Java Runtime Environment version for the currently executing JVM or if - * the version is {@link #OTHER} + * the version is {@link #OTHER}} * * @see #currentJre() * @see #currentVersionNumber() @@ -125,8 +125,8 @@ public enum JRE { } /** - * @return the {@link JRE} for the currently executing JVM, potentially - * {@link #OTHER} + * {@return the {@link JRE} for the currently executing JVM, potentially + * {@link #OTHER}} * * @since 5.7 * @see #currentVersionNumber() @@ -139,8 +139,8 @@ public enum JRE { } /** - * @return the {@link JRE} for the currently executing JVM, potentially - * {@link #OTHER} + * {@return the {@link JRE} for the currently executing JVM, potentially + * {@link #OTHER}} * * @since 5.12 * @see #currentVersionNumber() @@ -156,8 +156,8 @@ public enum JRE { } /** - * @return the version number for the currently executing JVM, or {@code -1} - * if the current JVM version could not be determined + * {@return the version number for the currently executing JVM, or {@code -1} + * if the current JVM version could not be determined} * * @since 5.12 * @see Runtime.Version#feature() @@ -169,10 +169,10 @@ public enum JRE { } /** - * @return {@code true} if the supplied version number is known to be the + * {@return {@code true} if the supplied version number is known to be the * Java Runtime Environment version for the currently executing JVM or if * the supplied version number is {@code -1} and the current JVM version - * could not be determined + * could not be determined} * * @since 5.12 * @see Runtime.Version#feature() diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index d4b354b09d0e..6680c28f6b94 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -10,7 +10,6 @@ package org.junit.jupiter.engine.descriptor; -import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.createDisplayNameSupplierForNestedClass; import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; @@ -92,9 +91,9 @@ public static List> getEnclosingTestClasses(@Nullable TestDescriptor pa if (parent instanceof TestClassAware testClassAwareParent) { List> result = new ArrayList<>(testClassAwareParent.getEnclosingTestClasses()); result.add(testClassAwareParent.getTestClass()); - return result; + return List.copyOf(result); } - return emptyList(); + return List.of(); } // --- ClassBasedTestDescriptor -------------------------------------------- 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 416131d853cf..27a0b5fc2f60 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 @@ -119,10 +119,10 @@ T apply(InvocationInterceptor interceptor, Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable; static ReflectiveInterceptorCall ofVoidMethod(VoidMethodInterceptorCall call) { - return ((interceptorChain, invocation, invocationContext, extensionContext) -> { + return (interceptorChain, invocation, invocationContext, extensionContext) -> { call.apply(interceptorChain, invocation, invocationContext, extensionContext); return null; - }); + }; } interface VoidMethodInterceptorCall { 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 d352e94a358a..a20c136fe73a 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 @@ -526,6 +526,7 @@ private IOException createIOExceptionWithAttachedFailures(SortedMap optional) { return optional != null ? optional.orElse(null) : null; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java index 42831f0e737d..cce55eb24638 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -126,7 +127,7 @@ Optional getDefaultTimeoutThreadMode() { private Optional parseTimeoutThreadModeConfiguration() { return extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME).map(value -> { try { - ThreadMode threadMode = ThreadMode.valueOf(value.toUpperCase()); + ThreadMode threadMode = ThreadMode.valueOf(value.toUpperCase(Locale.ROOT)); if (threadMode == ThreadMode.INFERRED) { logger.warn( () -> "Invalid timeout thread mode '%s', only %s and %s can be used as configuration parameter for %s.".formatted( diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java index 66c154faba5a..6643c16b7d60 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java @@ -12,6 +12,7 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.Locale; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Timeout; @@ -35,7 +36,7 @@ static TimeoutDuration from(Timeout timeout) { @Override public String toString() { - String label = unit.name().toLowerCase(); + String label = unit.name().toLowerCase(Locale.ROOT); if (value == 1 && label.endsWith("s")) { label = label.substring(0, label.length() - 1); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java index 41312b59d622..290da23062d0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java @@ -80,7 +80,7 @@ public void close() throws Exception { @SuppressWarnings("try") static class SingleThreadExecutorResource extends ExecutorResource { - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "ThreadPriorityCheck" }) SingleThreadExecutorResource() { super(Executors.newSingleThreadScheduledExecutor(runnable -> { Thread thread = new Thread(runnable, "junit-jupiter-timeout-watcher"); 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 ec84443b488d..e4a7e4e9bb0a 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 @@ -10,8 +10,6 @@ package org.junit.jupiter.migrationsupport.rules; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; @@ -57,13 +55,13 @@ public ExpectedExceptionSupport() { @Override public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { - getStore(context).put(EXCEPTION_WAS_HANDLED, TRUE); + getStore(context).put(EXCEPTION_WAS_HANDLED, true); this.support.handleTestExecutionException(context, throwable); } @Override public void afterEach(ExtensionContext context) throws Exception { - Boolean handled = getStore(context).getOrComputeIfAbsent(EXCEPTION_WAS_HANDLED, key -> FALSE, Boolean.class); + 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-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java index b6002a4ad817..b55964af555b 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java @@ -60,14 +60,14 @@ class ParameterizedClassContext implements ParameterizedDeclarationContext(findLifecycleMethodsAndAssertStaticAndNonPrivate(testClass, - testInstanceLifecycle, BOTTOM_UP, AfterParameterizedClassInvocation.class, - AfterParameterizedClassInvocation::injectArguments, this.resolverFacade)); + this.afterMethods = new ArrayList<>(findLifecycleMethodsAndAssertStaticAndNonPrivate(testClass, BOTTOM_UP, + AfterParameterizedClassInvocation.class, AfterParameterizedClassInvocation::injectArguments, + this.resolverFacade)); // Since the bottom-up ordering of afterMethods will later be reversed when the // AfterParameterizedClassInvocationMethodInvoker extensions are executed within @@ -146,8 +146,8 @@ List getAfterMethods() { } private static List findLifecycleMethodsAndAssertStaticAndNonPrivate( - Class testClass, TestInstance.Lifecycle testInstanceLifecycle, HierarchyTraversalMode traversalMode, - Class annotationType, Predicate injectArgumentsPredicate, ResolverFacade resolverFacade) { + Class testClass, HierarchyTraversalMode traversalMode, Class annotationType, + Predicate injectArgumentsPredicate, ResolverFacade resolverFacade) { List methods = findAnnotatedMethods(testClass, annotationType, traversalMode); 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 4df551843e4d..d459709fde13 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 @@ -98,6 +98,7 @@ String format(int invocationIndex, EvaluatedArgumentSet arguments) { } } + @SuppressWarnings("JdkObsolete") private String formatSafely(int invocationIndex, EvaluatedArgumentSet arguments) { ArgumentsContext context = new ArgumentsContext(invocationIndex, arguments.getConsumedNames(), arguments.getName()); @@ -200,6 +201,7 @@ private static String argumentsPattern(int length) { private record PlaceholderPosition(int index, String placeholder) { } + @SuppressWarnings("ArrayRecordComponent") private record ArgumentsContext(int invocationIndex, @Nullable Object[] consumedArguments, Optional argumentSetName) { } 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 3aebf1fe27b1..9980fe7eefbf 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 @@ -234,8 +234,8 @@ ArgumentSetLifecycleMethod.ParameterResolver createLifecycleMethodParameterResol ResolverFacade lifecycleMethodResolverFacade = create(method, annotation); Map parameterDeclarationMapping = new HashMap<>(); - List errors = validateLifecycleMethodParameters(method, annotation, originalResolverFacade, - lifecycleMethodResolverFacade, parameterDeclarationMapping); + List errors = validateLifecycleMethodParameters(originalResolverFacade, lifecycleMethodResolverFacade, + parameterDeclarationMapping); return Try // .call(() -> configurationErrorOrSuccess(errors, @@ -336,8 +336,8 @@ private static NavigableMap validateFieldDec .collect(toMap(Map.Entry::getKey, entry -> entry.getValue().get(0), (d, __) -> d, TreeMap::new))); } - private static List validateLifecycleMethodParameters(Method method, Annotation annotation, - ResolverFacade originalResolverFacade, ResolverFacade lifecycleMethodResolverFacade, + private static List validateLifecycleMethodParameters(ResolverFacade originalResolverFacade, + ResolverFacade lifecycleMethodResolverFacade, Map parameterDeclarationMapping) { List actualDeclarations = lifecycleMethodResolverFacade.indexedParameterDeclarations.getAll(); List errors = new ArrayList<>(); 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 9a7c4ee47184..9f270d350387 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 @@ -103,6 +103,7 @@ private static List getHeaders(CsvRecord record) { return ((NamedCsvRecord) record).getHeader(); } + @SuppressWarnings({ "ReferenceEquality", "StringEquality" }) private static @Nullable String resolveNullMarker(String record) { return record == CsvReaderFactory.DefaultFieldModifier.NULL_MARKER ? null : record; } 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 115ccb8e8ef2..7ac71abb59dc 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 @@ -249,7 +249,7 @@ public interface Transformer extends Try { + private static final class Success extends Try { private final V value; @@ -327,7 +327,7 @@ public int hashCode() { } } - private static class Failure extends Try { + private static final class Failure extends Try { private final Exception cause; 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 e5ac90f9d8fb..f530db714ce7 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,7 +75,7 @@ public Stream stream() { * @see #stream(Class) * @see #stream(Class, Level) */ - @SuppressWarnings("ConstantValue") + @SuppressWarnings({ "ConstantValue", "ReferenceEquality" }) public Stream stream(Level level) { // NOTE: we cannot use org.junit.platform.commons.util.Preconditions here // since that would introduce a package cycle. @@ -129,7 +129,7 @@ public Stream stream(Class clazz) { * @see #stream(Level) * @see #stream(Class) */ - @SuppressWarnings("ConstantValue") + @SuppressWarnings({ "ConstantValue", "ReferenceEquality" }) 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/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java index 24044ac09e27..97e245989f26 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 @@ -70,6 +70,7 @@ private AnnotationSupport() { * @see #findRepeatableAnnotations(Optional, Class) */ @API(status = MAINTAINED, since = "1.3") + @SuppressWarnings("NullableOptional") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { @@ -112,6 +113,7 @@ public static boolean isAnnotated(@Nullable AnnotatedElement element, Class Optional findAnnotation( @Nullable Optional element, Class annotationType) { @@ -253,6 +255,7 @@ public static Optional findAnnotation(@Nullable Class< * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ @API(status = MAINTAINED, since = "1.5") + @SuppressWarnings("NullableOptional") public static List findRepeatableAnnotations( @Nullable Optional element, Class annotationType) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java index 16d545daf141..28b76fb19a2c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java @@ -10,7 +10,6 @@ package org.junit.platform.commons.support.scanning; -import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.scanning.ClasspathFilters.CLASS_FILE_SUFFIX; @@ -341,19 +340,18 @@ private static String packagePath(String packageName) { } private List getRootUrisForPackage(String basePackageName) { + List uris = new ArrayList<>(); try { Enumeration resources = getClassLoader().getResources(packagePath(basePackageName)); - List uris = new ArrayList<>(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); uris.add(resource.toURI()); } - return uris; } catch (Exception ex) { logger.warn(ex, () -> "Error reading URIs from class loader for base package " + basePackageName); - return emptyList(); } + return uris; } } 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 0e7d7b7e2441..d277c250cfd4 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 @@ -326,7 +326,7 @@ private static void findRepeatableAnnotations(AnnotatedEl findRepeatableAnnotations(element.getAnnotations(), annotationType, containerType, inherited, found, visited); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "GetClassOnAnnotation" }) private static void findRepeatableAnnotations(Annotation[] candidates, Class annotationType, Class containerType, boolean inherited, Set found, Set visited) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java index 8874905af32a..3f68495d2393 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java @@ -76,7 +76,7 @@ public static RuntimeException throwAsUncheckedException(Throwable t) { return ExceptionUtils.throwAs(t); } - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" }) private static T throwAs(Throwable t) throws T { throw (T) t; } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 103b8f2fd234..ecb187cdb48a 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 @@ -217,6 +217,11 @@ public enum HierarchyTraversalMode { classNameToTypeMap = Collections.unmodifiableMap(classNamesToTypes); + primitiveToWrapperMap = createPrimitivesToWrapperMap(); + } + + @SuppressWarnings("IdentityHashMapUsage") + private static Map, Class> createPrimitivesToWrapperMap() { Map, Class> primitivesToWrappers = new IdentityHashMap<>(8); primitivesToWrappers.put(boolean.class, Boolean.class); @@ -228,7 +233,7 @@ public enum HierarchyTraversalMode { primitivesToWrappers.put(float.class, Float.class); primitivesToWrappers.put(double.class, Double.class); - primitiveToWrapperMap = Collections.unmodifiableMap(primitivesToWrappers); + return Collections.unmodifiableMap(primitivesToWrappers); } public static boolean isPublic(Class clazz) { @@ -637,13 +642,12 @@ public static Try tryToReadFieldValue(Class clazz, String fieldNa * @see #tryToReadFieldValue(Class, String, Object) */ @API(status = INTERNAL, since = "1.4") - @SuppressWarnings("NullAway") 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)); - return Try.call(() -> makeAccessible(field).get(instance)); + return Try.<@Nullable Object> call(() -> makeAccessible(field).get(instance)); } /** @@ -1421,6 +1425,7 @@ public static Try tryToGetMethod(Class clazz, String methodName, Clas * @since 1.11 */ @API(status = INTERNAL, since = "1.11") + @SuppressWarnings("ReferenceEquality") public static Method getInterfaceMethodIfPossible(Method method, @Nullable Class targetClass) { if (!isPublic(method) || method.getDeclaringClass().isInterface()) { return method; 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 6f63ab1d7432..49818e769298 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 @@ -32,6 +32,7 @@ public static void executeConcurrently(int threads, Runnable action) throws Exce }); } + @SuppressWarnings("Finally") public static List executeConcurrently(int threads, Callable action) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(threads); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java index bd39bff687da..d470acbd7963 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java @@ -35,6 +35,7 @@ public class ConsoleUtils { /** * {@return the charset of the console} */ + @SuppressWarnings("SystemConsoleNull") public static Charset charset() { Console console = System.console(); return console != null ? console.charset() : Charset.defaultCharset(); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java index 19008f76425c..b699385c2cc1 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java @@ -12,6 +12,8 @@ import static org.apiguardian.api.API.Status.INTERNAL; +import java.util.Locale; + import org.apiguardian.api.API; /** @@ -58,7 +60,7 @@ public enum Details { */ @Override public String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java index 66c7f6f14d89..b36e8dc437ec 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java @@ -73,6 +73,7 @@ public DirectorySelector convert(String value) { } } + @SuppressWarnings("JavaLangClash") static class Package implements ITypeConverter { @Override public PackageSelector convert(String value) { @@ -80,6 +81,7 @@ public PackageSelector convert(String value) { } } + @SuppressWarnings("JavaLangClash") static class Class implements ITypeConverter { @Override public ClassSelector convert(String value) { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java index 1abf7f8e0084..dca56000bb76 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java @@ -14,6 +14,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Locale; import org.apiguardian.api.API; import org.junit.platform.engine.TestExecutionResult; @@ -129,7 +130,7 @@ public final String status(TestExecutionResult result) { */ @Override public final String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java index 120f2de48a6f..34c1aadd287c 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java @@ -13,9 +13,11 @@ import java.io.FileReader; import java.io.IOException; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; import java.util.EnumMap; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.function.Function; @@ -89,8 +91,8 @@ private ColorPalette(Map colorsToAnsiSequences, boolean disableAn } private static Map toOverrideMap(Properties properties) { - Map upperCaseProperties = properties.entrySet().stream().collect( - Collectors.toMap(entry -> ((String) entry.getKey()).toUpperCase(), entry -> (String) entry.getValue())); + Map upperCaseProperties = properties.entrySet().stream().collect(Collectors.toMap( + entry -> ((String) entry.getKey()).toUpperCase(Locale.ROOT), entry -> (String) entry.getValue())); return Arrays.stream(Style.values()).filter(style -> upperCaseProperties.containsKey(style.name())).collect( Collectors.toMap(Function.identity(), style -> upperCaseProperties.get(style.name()))); @@ -108,7 +110,7 @@ private static Properties getProperties(Reader reader) { } private static Properties getProperties(Path path) { - try (FileReader fileReader = new FileReader(path.toFile())) { + try (FileReader fileReader = new FileReader(path.toFile(), StandardCharsets.UTF_8)) { return getProperties(fileReader); } catch (IOException e) { 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 eabfcfe20815..7007436f4d9b 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 @@ -80,14 +80,14 @@ private FilterResult(boolean included, @Nullable String reason) { } /** - * @return {@code true} if the filtered object should be included + * {@return {@code true} if the filtered object should be included} */ public boolean included() { return this.included; } /** - * @return {@code true} if the filtered object should be excluded + * {@return {@code true} if the filtered object should be excluded} */ public boolean excluded() { return !included(); 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 2039ecf8ba11..8d146c05b616 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 @@ -371,15 +371,15 @@ enum Type { CONTAINER_AND_TEST; /** - * @return {@code true} if this type represents a descriptor that can - * contain other descriptors + * {@return {@code true} if this type represents a descriptor that can + * contain other descriptors} */ public boolean isContainer() { return this == CONTAINER || this == CONTAINER_AND_TEST; } /** - * @return {@code true} if this type represents a descriptor for a test + * {@return {@code true} if this type represents a descriptor for a test} */ public boolean isTest() { return this == TEST || this == CONTAINER_AND_TEST; 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 c60e716d54ec..d98070bafb78 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 @@ -35,7 +35,7 @@ * @since 1.0 */ @API(status = STABLE, since = "1.0") -public class UniqueId implements Cloneable, Serializable { +public final class UniqueId implements Cloneable, Serializable { @Serial private static final long serialVersionUID = 1L; @@ -295,7 +295,7 @@ public String toString() { * value. */ @API(status = STABLE, since = "1.0") - public static class Segment implements Serializable { + public static final class Segment implements Serializable { @Serial private static final long serialVersionUID = 1L; 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 82a0b305ee65..2a84df0d55ba 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 @@ -46,7 +46,7 @@ * @see org.junit.platform.engine.support.descriptor.ClassSource */ @API(status = STABLE, since = "1.0") -public class ClassSelector implements DiscoverySelector { +public final class ClassSelector implements DiscoverySelector { private final @Nullable ClassLoader classLoader; 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 a7f83f3b4715..5cd34a04a990 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 @@ -53,7 +53,7 @@ * @see #getClasspathResourceName() */ @API(status = STABLE, since = "1.0") -public class ClasspathResourceSelector implements DiscoverySelector { +public final class ClasspathResourceSelector implements DiscoverySelector { private final String classpathResourceName; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java index 27708e10f8f2..17fea2805930 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java @@ -44,7 +44,7 @@ * @see Thread#getContextClassLoader() */ @API(status = STABLE, since = "1.0") -public class ClasspathRootSelector implements DiscoverySelector { +public final class ClasspathRootSelector implements DiscoverySelector { private final URI classpathRoot; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java index 577b97bc93a1..f6e9a3cc92eb 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java @@ -40,7 +40,7 @@ * @see #getRawPath() */ @API(status = STABLE, since = "1.0") -public class DirectorySelector implements DiscoverySelector { +public final class DirectorySelector implements DiscoverySelector { private final String path; 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 74973ef3fa90..fcf3f1f0889c 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 @@ -37,7 +37,7 @@ * @since 1.7 */ @API(status = STABLE, since = "1.7") -public class FilePosition implements Serializable { +public final class FilePosition implements Serializable { @Serial private static final long serialVersionUID = 1L; 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 ac01e74b5379..e90a75ade8e6 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 @@ -42,7 +42,7 @@ * @see #getRawPath() */ @API(status = STABLE, since = "1.0") -public class FileSelector implements DiscoverySelector { +public final class FileSelector implements DiscoverySelector { private final String path; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java index 8b63b8a01a77..0d06e7c3a264 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java @@ -45,7 +45,7 @@ * @see DiscoverySelectors#selectIteration(DiscoverySelector, int...) */ @API(status = MAINTAINED, since = "1.13.3") -public class IterationSelector implements DiscoverySelector { +public final class IterationSelector implements DiscoverySelector { private final DiscoverySelector parentSelector; private final SortedSet iterationIndices; 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 74efbcf9c5f2..27fd6aea479a 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 @@ -58,7 +58,7 @@ * @see org.junit.platform.engine.support.descriptor.MethodSource */ @API(status = STABLE, since = "1.0") -public class MethodSelector implements DiscoverySelector { +public final class MethodSelector implements DiscoverySelector { private final @Nullable ClassLoader classLoader; private final String className; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java index 0cac7f2e3bfe..1ecd5d7de8fa 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java @@ -31,7 +31,7 @@ * @see DiscoverySelectors#selectModules(java.util.Set) */ @API(status = STABLE, since = "1.1") -public class ModuleSelector implements DiscoverySelector { +public final class ModuleSelector implements DiscoverySelector { private final String moduleName; 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 30996cd3e167..b24e907c0285 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 @@ -51,7 +51,7 @@ * @see ClassSelector */ @API(status = STABLE, since = "1.6") -public class NestedClassSelector implements DiscoverySelector { +public final class NestedClassSelector implements DiscoverySelector { private final @Nullable ClassLoader 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 9fc6415b63a7..c796e862cb01 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 @@ -56,7 +56,7 @@ * @see MethodSelector */ @API(status = STABLE, since = "1.6") -public class NestedMethodSelector implements DiscoverySelector { +public final class NestedMethodSelector implements DiscoverySelector { private final NestedClassSelector nestedClassSelector; private final MethodSelector methodSelector; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java index 8f84886873cf..b95b89c43077 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java @@ -31,7 +31,7 @@ * @see org.junit.platform.engine.support.descriptor.PackageSource */ @API(status = STABLE, since = "1.0") -public class PackageSelector implements DiscoverySelector { +public final class PackageSelector implements DiscoverySelector { private final String packageName; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java index ebf1e35697ba..9677e1c4976b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java @@ -32,7 +32,7 @@ * @see DiscoverySelectors#selectUniqueId(UniqueId) */ @API(status = STABLE, since = "1.0") -public class UniqueIdSelector implements DiscoverySelector { +public final class UniqueIdSelector implements DiscoverySelector { private final UniqueId uniqueId; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java index 1c7b635a915e..1d4521679b16 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java @@ -35,7 +35,7 @@ * @see org.junit.platform.engine.support.descriptor.UriSource */ @API(status = STABLE, since = "1.0") -public class UriSelector implements DiscoverySelector { +public final class UriSelector implements DiscoverySelector { private final URI uri; 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 3d02c01f797a..5627479185f1 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 @@ -14,6 +14,7 @@ import java.nio.file.Path; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Optional; import org.apiguardian.api.API; @@ -43,7 +44,7 @@ public static FileEntry from(Path path, @Nullable String mediaType) { return new FileEntry(path, mediaType); } - private final LocalDateTime timestamp = LocalDateTime.now(); + private final LocalDateTime timestamp = LocalDateTime.now(ZoneId.systemDefault()); private final Path path; private final @Nullable String mediaType; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java index 28587cfe0615..2dfc47d49683 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; @@ -32,7 +33,7 @@ @API(status = STABLE, since = "1.0") public final class ReportEntry { - private final LocalDateTime timestamp = LocalDateTime.now(); + private final LocalDateTime timestamp = LocalDateTime.now(ZoneId.systemDefault()); private final Map keyValuePairs = new LinkedHashMap<>(); private ReportEntry() { 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 4292bc7f6028..645b835eb95b 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 @@ -190,6 +190,7 @@ public final int hashCode() { } @Override + @SuppressWarnings("EqualsGetClass") public final boolean equals(Object other) { if (other == null) { return false; 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 7852ba45b8a0..07304feec0e1 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 @@ -43,7 +43,7 @@ * @see org.junit.platform.engine.discovery.ClassSelector */ @API(status = STABLE, since = "1.0") -public class ClassSource implements TestSource { +public final class ClassSource implements TestSource { @Serial private static final long serialVersionUID = 1L; 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 838b92d9ad8e..988f8c314e2b 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 @@ -33,7 +33,7 @@ * @see org.junit.platform.engine.discovery.ClasspathResourceSelector */ @API(status = STABLE, since = "1.0") -public class ClasspathResourceSource implements TestSource { +public final class ClasspathResourceSource implements TestSource { @Serial private static final long serialVersionUID = 1L; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java index 15438b93c4b1..59b94cc5ca90 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java @@ -29,7 +29,7 @@ * @since 1.0 */ @API(status = STABLE, since = "1.0") -public class CompositeTestSource implements TestSource { +public final class CompositeTestSource implements TestSource { @Serial private static final long serialVersionUID = 1L; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java index a2b3645bd203..79505ee177fd 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java @@ -29,7 +29,7 @@ * @see org.junit.platform.engine.discovery.DirectorySelector */ @API(status = STABLE, since = "1.0") -public class DirectorySource implements FileSystemSource { +public final class DirectorySource implements FileSystemSource { @Serial private static final long serialVersionUID = 1L; 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 7d8d9b5500ff..65b0bae08118 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 @@ -32,7 +32,7 @@ * @since 1.0 */ @API(status = STABLE, since = "1.0") -public class FilePosition implements Serializable { +public final class FilePosition implements Serializable { @Serial private static final long serialVersionUID = 1L; 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 577f602659db..dd253f6a0087 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 @@ -33,7 +33,7 @@ * @see org.junit.platform.engine.discovery.FileSelector */ @API(status = STABLE, since = "1.0") -public class FileSource implements FileSystemSource { +public final class FileSource implements FileSystemSource { @Serial private static final long serialVersionUID = 1L; 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 1917dd19b168..cde3533a72b0 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 @@ -36,7 +36,7 @@ * @see org.junit.platform.engine.discovery.MethodSelector */ @API(status = STABLE, since = "1.0") -public class MethodSource implements TestSource { +public final class MethodSource implements TestSource { @Serial private static final long serialVersionUID = 1L; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java index d3c46953a3f9..94befac98143 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java @@ -30,7 +30,7 @@ * @see org.junit.platform.engine.discovery.PackageSelector */ @API(status = STABLE, since = "1.0") -public class PackageSource implements TestSource { +public final class PackageSource implements TestSource { @Serial private static final long serialVersionUID = 1L; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.java index bfe4a854902c..ffebf29bd580 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/DiscoveryIssueReporter.java @@ -165,6 +165,7 @@ static Condition alwaysSatisfied() { * * @return the composed condition; never {@code null} */ + @SuppressWarnings("ShortCircuitBoolean") default Condition and(Condition that) { Preconditions.notNull(that, "condition must not be null"); return value -> this.check(value) & that.check(value); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java index 3373f9259f4e..fdb0361862b9 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java @@ -92,6 +92,7 @@ public LockMode getLockMode() { } @Override + @SuppressWarnings("EqualsGetClass") public boolean equals(Object o) { if (this == o) { return true; 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 60677dea7bac..ced88fc1b946 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 @@ -21,7 +21,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; @@ -161,9 +160,9 @@ public void invokeAll(List tasks) { new ExclusiveTask(tasks.get(0)).execSync(); return; } - Deque isolatedTasks = new LinkedList<>(); - Deque sameThreadTasks = new LinkedList<>(); - Deque concurrentTasksInReverseOrder = new LinkedList<>(); + Deque isolatedTasks = new ArrayDeque<>(); + Deque sameThreadTasks = new ArrayDeque<>(); + Deque concurrentTasksInReverseOrder = new ArrayDeque<>(); forkConcurrentTasks(tasks, isolatedTasks, sameThreadTasks, concurrentTasksInReverseOrder); executeSync(sameThreadTasks); joinConcurrentTasksInReverseOrderToEnableWorkStealing(concurrentTasksInReverseOrder); 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 321c3df0027b..1f09383680fd 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 @@ -241,6 +241,7 @@ private class DefaultDynamicTestExecutor implements DynamicTestExecutor { private final Map unfinishedTasks = new ConcurrentHashMap<>(); @Override + @SuppressWarnings("FutureReturnValueIgnored") public void execute(TestDescriptor testDescriptor) { execute(testDescriptor, taskContext.listener()); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java index d18935b75c4f..9f5a885c0b2f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/Namespace.java @@ -28,7 +28,7 @@ * lifecycle of a single extension. */ @API(status = EXPERIMENTAL, since = "6.0") -public class Namespace { +public final class Namespace { /** * The default, global namespace which allows access to stored data from 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 6cc56a0dd9af..335fc24dafdc 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 @@ -52,6 +52,7 @@ public final class TestIdentifier implements Serializable { @Serial private static final long serialVersionUID = 1L; @Serial + @SuppressWarnings("UnusedVariable") private static final ObjectStreamField[] serialPersistentFields = ObjectStreamClass.lookup( SerializedForm.class).getFields(); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 7d2e7c529d4e..5c4a2f779ae9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -15,9 +15,9 @@ import static org.junit.platform.engine.Filter.composeFilters; import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -177,7 +177,7 @@ private Map discoverSafely(LauncherDiscoveryReques engineFilterer.performSanityChecks(); - List filters = new LinkedList<>(postDiscoveryFilters); + List filters = new ArrayList<>(postDiscoveryFilters); filters.addAll(request.getPostDiscoveryFilters()); applyPostDiscoveryFilters(testEngineDescriptors, filters); @@ -238,7 +238,7 @@ private void applyPostDiscoveryFilters(Map testEng private void populateExclusionReasonInMap(Optional reason, TestDescriptor testDescriptor, Map> excludedTestDescriptorsByReason) { - excludedTestDescriptorsByReason.computeIfAbsent(reason.orElse("Unknown"), list -> new LinkedList<>()).add( + excludedTestDescriptorsByReason.computeIfAbsent(reason.orElse("Unknown"), __ -> new ArrayList<>()).add( testDescriptor); } 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 be3128ed4758..7d105169d7c0 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 @@ -12,14 +12,16 @@ import static java.util.stream.Collectors.joining; +import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -34,6 +36,7 @@ import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.CollectionUtils; +import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.ConfigurationParameters; @@ -252,32 +255,9 @@ private static Properties loadClasspathResource(String configFileName) { Properties props = new Properties(); try { - ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader(); - Set resources = new LinkedHashSet<>(Collections.list(classLoader.getResources(configFileName))); - - if (!resources.isEmpty()) { - - URL configFileUrl = CollectionUtils.getFirstElement(resources).get(); - - if (resources.size() > 1) { - logger.warn(() -> { - String formattedResourceList = Stream.concat( // - Stream.of(configFileUrl + " (*)"), // - resources.stream().skip(1).map(URL::toString) // - ).collect(joining("\n- ", "\n- ", "")); - return "Discovered %d '%s' configuration files on the classpath (see below); only the first (*) will be used.%s".formatted( - resources.size(), configFileName, formattedResourceList); - }); - } - - logger.config( - () -> "Loading JUnit Platform configuration parameters from classpath resource [%s].".formatted( - configFileUrl)); - URLConnection urlConnection = configFileUrl.openConnection(); - urlConnection.setUseCaches(false); - try (InputStream inputStream = urlConnection.getInputStream()) { - props.load(inputStream); - } + URL configFileUrl = findConfigFile(configFileName); + if (configFileUrl != null) { + loadClasspathResource(configFileUrl, props); } } catch (Exception ex) { @@ -289,4 +269,57 @@ private static Properties loadClasspathResource(String configFileName) { return props; } + private static @Nullable URL findConfigFile(String configFileName) throws IOException { + + ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader(); + List urls = Collections.list(classLoader.getResources(configFileName)); + + if (urls.size() == 1) { + return urls.get(0); + } + + if (urls.size() > 1) { + + List resources = urls.stream() // + .map(LauncherConfigurationParameters::toURI) // + .distinct() // + .toList(); + + URL configFileUrl = resources.get(0).toURL(); + + if (resources.size() > 1) { + logger.warn(() -> { + String formattedResourceList = Stream.concat( // + Stream.of(configFileUrl + " (*)"), // + resources.stream().skip(1).map(URI::toString) // + ).collect(joining("\n- ", "\n- ", "")); + return "Discovered %d '%s' configuration files on the classpath (see below); only the first (*) will be used.%s".formatted( + resources.size(), configFileName, formattedResourceList); + }); + } + return configFileUrl; + } + + return null; + } + + private static void loadClasspathResource(URL configFileUrl, Properties props) throws IOException { + logger.config(() -> "Loading JUnit Platform configuration parameters from classpath resource [%s].".formatted( + configFileUrl)); + URLConnection urlConnection = configFileUrl.openConnection(); + urlConnection.setUseCaches(false); + try (InputStream inputStream = urlConnection.getInputStream()) { + props.load(inputStream); + } + } + + private static URI toURI(URL url) { + try { + return url.toURI(); + } + catch (URISyntaxException e) { + throw ExceptionUtils.throwAsUncheckedException(e); + } + } + } 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 60a3d6a8cc9a..d10db3855648 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 @@ -12,6 +12,7 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.nio.charset.Charset; import java.util.ArrayDeque; import java.util.Deque; import java.util.Optional; @@ -138,7 +139,7 @@ String rewind() { } int length = count - position; count -= length; - return new String(buf, position, length); + return new String(buf, position, length, Charset.defaultCharset()); } } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java index 34be95e19c12..925c437d5f1a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java @@ -60,12 +60,7 @@ private static OutputDir createSafely(Optional customDir, Supplier Path outputDir; if (customDir.isPresent() && StringUtils.isNotBlank(customDir.get())) { - String customPath = customDir.get(); - while (customPath.contains(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)) { - customPath = OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN.matcher(customPath) // - .replaceFirst(String.valueOf(Math.abs(random.nextLong()))); - } - outputDir = cwd.resolve(customPath); + outputDir = cwd.resolve(expandPlaceholders(customDir.get(), random)); } else if (Files.exists(cwd.resolve("pom.xml"))) { outputDir = cwd.resolve("target"); @@ -84,6 +79,15 @@ else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { return new OutputDir(outputDir.normalize(), random); } + private static String expandPlaceholders(String customDir, SecureRandom random) { + String customPath = customDir; + while (customPath.contains(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)) { + customPath = OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN.matcher(customPath) // + .replaceFirst(String.valueOf(positiveLong(random))); + } + return customPath; + } + private final Path path; private final SecureRandom random; @@ -97,7 +101,7 @@ public Path toPath() { } public Path createFile(String prefix, String extension) throws UncheckedIOException { - String filename = "%s-%d.%s".formatted(prefix, Math.abs(random.nextLong()), extension); + String filename = "%s-%d.%s".formatted(prefix, positiveLong(random), extension); Path outputFile = path.resolve(filename); try { @@ -111,6 +115,15 @@ public Path createFile(String prefix, String extension) throws UncheckedIOExcept } } + private static long positiveLong(SecureRandom random) { + var value = random.nextLong(); + if (value == Long.MIN_VALUE) { + // ensure Math.abs returns positive value + value++; + } + return Math.abs(value); + } + /** * Determine if the supplied directory contains files with any of the * supplied extensions. diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java index 3892a8ae565c..ce38669105eb 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java @@ -19,7 +19,7 @@ * @since 1.6 * @see LauncherDiscoveryListeners#abortOnFailure() */ -class AbortOnFailureLauncherDiscoveryListener implements LauncherDiscoveryListener { +final class AbortOnFailureLauncherDiscoveryListener implements LauncherDiscoveryListener { @Override public void engineDiscoveryFinished(UniqueId engineId, EngineDiscoveryResult result) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java index 922b9068b2e4..1e1f008cbda3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java @@ -31,7 +31,7 @@ * @since 1.6 * @see LauncherDiscoveryListeners#logging() */ -class LoggingLauncherDiscoveryListener implements LauncherDiscoveryListener { +final class LoggingLauncherDiscoveryListener implements LauncherDiscoveryListener { private static final Logger logger = LoggerFactory.getLogger(LoggingLauncherDiscoveryListener.class); 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 8a1922012d2d..18eb18fab029 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 @@ -111,7 +111,7 @@ private ParseStatus findMatchingLeftParenthesis(Token token) { private ParseStatus findOperands(Token token, Operator currentOperator) { while (currentOperator.hasLowerPrecedenceThan(previousOperator()) - || currentOperator.hasSamePrecedenceAs(previousOperator()) && currentOperator.isLeftAssociative()) { + || (currentOperator.hasSamePrecedenceAs(previousOperator()) && currentOperator.isLeftAssociative())) { TokenWith tokenWithWithOperator = operators.pop(); ParseStatus parseStatus = tokenWithWithOperator.element().createAndAddExpressionTo(expressions, tokenWithWithOperator.token()); 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 57a5e6cbe687..34a236838dcc 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 @@ -10,7 +10,6 @@ package org.junit.platform.launcher.tagexpression; -import static java.util.Collections.emptyList; import static java.util.regex.Pattern.CASE_INSENSITIVE; import java.util.ArrayList; @@ -30,14 +29,14 @@ class Tokenizer { List tokenize(@Nullable String infixTagExpression) { if (infixTagExpression == null) { - return emptyList(); + return List.of(); } List parts = new ArrayList<>(); Matcher matcher = PATTERN.matcher(infixTagExpression); while (matcher.find()) { parts.add(new Token(matcher.start(), matcher.group())); } - return parts; + return List.copyOf(parts); } } 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 cf96452b202d..0638788519d0 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 @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Clock; +import java.time.ZoneId; import javax.xml.stream.XMLStreamException; @@ -53,7 +54,7 @@ public class LegacyXmlReportGeneratingListener implements TestExecutionListener private @Nullable XmlReportData reportData; public LegacyXmlReportGeneratingListener(Path reportsDir, PrintWriter out) { - this(reportsDir, out, Clock.systemDefaultZone()); + this(reportsDir, out, Clock.system(ZoneId.systemDefault())); } // For tests only 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 94c1a128c57e..d0af0bdf8977 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 @@ -149,6 +149,7 @@ private boolean isGitEnabled(ConfigurationParameters config) { return config.getBoolean(GIT_ENABLED_PROPERTY_NAME).orElse(false); } + @SuppressWarnings("EmptyCatch") private void reportInfrastructure(ConfigurationParameters config) { eventsFileWriter.append(infrastructure(), infrastructure -> { try { diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java index 744ff5a3aed3..5755925f4234 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java @@ -376,6 +376,7 @@ public Events debug() { * @param out the {@code OutputStream} to print to; never {@code null} * @return this {@code Events} object for method chaining; never {@code null} */ + @SuppressWarnings("DefaultCharset") public Events debug(OutputStream out) { Preconditions.notNull(out, "OutputStream must not be null"); debug(new PrintWriter(out, true)); diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java index edfd4311fd67..2e7c3dd3ff32 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java @@ -205,6 +205,7 @@ public Executions debug() { * @param out the {@code OutputStream} to print to; never {@code null} * @return this {@code Executions} object for method chaining; never {@code null} */ + @SuppressWarnings("DefaultCharset") public Executions debug(OutputStream out) { Preconditions.notNull(out, "OutputStream must not be null"); debug(new PrintWriter(out, true)); @@ -217,6 +218,7 @@ public Executions debug(OutputStream out) { * @param writer the {@code Writer} to print to; never {@code null} * @return this {@code Executions} object for method chaining; never {@code null} */ + @SuppressWarnings("DefaultCharset") public Executions debug(Writer writer) { Preconditions.notNull(writer, "Writer must not be null"); debug(new PrintWriter(writer, true)); 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 c33c0afc7436..9993a73243ec 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 @@ -254,6 +254,7 @@ void add(VintageTestDescriptor descriptor) { * * @param description the {@code Description} to look up */ + @SuppressWarnings("ReferenceEquality") Optional getUnambiguously(Description description) { if (descriptors.isEmpty()) { return Optional.empty(); diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java index b3a864bce6ff..3a811314ea1f 100644 --- a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java @@ -55,6 +55,7 @@ ProcessResult waitFor() throws InterruptedException { } } + @SuppressWarnings("EmptyCatch") private static void closeQuietly(Optional fileStream) { if (fileStream.isEmpty()) { return; 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 c741388b6db6..6253915759c8 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -1,6 +1,7 @@ import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigurationInternal import junitbuild.extensions.capitalized import junitbuild.extensions.dependencyProject +import net.ltgt.gradle.errorprone.errorprone import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.jvm.toolchain.JvmVendorSpec.GRAAL_VM import org.gradle.kotlin.dsl.support.listFilesOrdered @@ -151,6 +152,12 @@ val archUnit by testing.suites.registering(JvmTestSuite::class) { } } +tasks.compileJava { + options.errorprone { + disableAllChecks = true + } +} + tasks.named("checkstyle${archUnit.name.capitalized()}").configure { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } From 7aec225af7211cf4d1b526c1ee64c3c2d4c490ef Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 09:03:53 +0200 Subject: [PATCH 059/162] Use text blocks and `String.formatted` and forbid `String.format` --- .../api/tools/AbstractApiReportWriter.java | 5 ++--- gradle/config/checkstyle/checkstyleMain.xml | 7 +++++++ gradle/config/checkstyle/checkstyleTest.xml | 7 +++++++ .../junit/jupiter/api/RandomOrdererUtils.java | 9 ++++----- .../support/TypeBasedParameterResolver.java | 7 +++---- .../EnumConfigurationParameterConverter.java | 8 ++++---- ...iatingConfigurationParameterConverter.java | 7 +++---- .../ClassTemplateTestDescriptor.java | 6 +++--- .../engine/descriptor/ExtensionUtils.java | 11 +++++------ .../TestTemplateTestDescriptor.java | 6 +++--- .../discovery/MethodSelectorResolver.java | 8 ++++---- .../predicates/IsTestFactoryMethod.java | 6 ++---- .../execution/ParameterResolutionUtils.java | 12 ++++++------ .../extension/RepeatedTestExtension.java | 5 +++-- .../engine/extension/TempDirectory.java | 6 +++--- .../params/ArgumentCountValidator.java | 7 +++---- .../ParameterizedTestSpiInstantiator.java | 6 +++--- .../junit/jupiter/params/ResolverFacade.java | 8 ++++---- .../aggregator/ArgumentsAggregator.java | 7 +++---- .../params/converter/ArgumentConverter.java | 7 +++---- .../AnnotationBasedArgumentsProvider.java | 8 ++++---- .../params/provider/ArgumentsProvider.java | 9 +++++---- .../params/provider/CsvArgumentsProvider.java | 3 +-- .../params/provider/CsvReaderFactory.java | 2 +- .../provider/FieldArgumentsProvider.java | 9 ++++----- .../provider/MethodArgumentsProvider.java | 9 ++++----- .../commons/util/ReflectionUtils.java | 19 +++++++++---------- .../tasks/TestFeedPrintingListener.java | 9 ++++----- .../tasks/VerboseTreePrintingListener.java | 4 ++-- .../support/hierarchical/NodeTestTask.java | 12 ++++++------ .../core/DiscoveryIssueCollector.java | 8 ++++---- .../ParameterizedTestExtensionTests.java | 6 +++--- 32 files changed, 122 insertions(+), 121 deletions(-) diff --git a/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java b/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java index 5dcc98bd6f06..5d2526b03f49 100644 --- a/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java +++ b/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java @@ -10,7 +10,6 @@ package org.junit.api.tools; -import static java.lang.String.format; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; @@ -84,8 +83,8 @@ protected void printDeclarationSectionHeader(Set statuses, Status status } out.println(h2("@API(%s)".formatted(status))); out.println(); - out.println( - paragraph(format("Discovered %d " + code("@API(%s)") + " declarations.", declarations.size(), status))); + out.println(paragraph( + "Discovered %d %s declarations.".formatted(declarations.size(), code("@API(%s)".formatted(status))))); out.println(); } diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index ed8dadaaff8d..5428e9d1e413 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -27,6 +27,13 @@ + + + + + + + diff --git a/gradle/config/checkstyle/checkstyleTest.xml b/gradle/config/checkstyle/checkstyleTest.xml index b795207938ad..2e81cea24a3b 100644 --- a/gradle/config/checkstyle/checkstyleTest.xml +++ b/gradle/config/checkstyle/checkstyleTest.xml @@ -19,6 +19,13 @@ + + + + + + + diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java index 16ca5250e539..4968973718e1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java @@ -41,11 +41,10 @@ private static Optional getCustomSeed(Function> c return Long.valueOf(configurationParameter); } catch (NumberFormatException ex) { - logger.warn(ex, - () -> String.format( - "Failed to convert configuration parameter [%s] with value [%s] to a long. " - + "Using default seed [%s] as fallback.", - RANDOM_SEED_PROPERTY_NAME, configurationParameter, DEFAULT_SEED)); + logger.warn(ex, () -> """ + Failed to convert configuration parameter [%s] with value [%s] to a long. \ + Using default seed [%s] as fallback.""".formatted(RANDOM_SEED_PROPERTY_NAME, + configurationParameter, DEFAULT_SEED)); return null; } }); 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 be80ed5c3a0a..432ba321063c 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 @@ -53,10 +53,9 @@ private Type getParameterType(ParameterContext parameterContext) { private Type enclosedTypeOfParameterResolver() { 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.", + findTypeBasedParameterResolverSuperclass(getClass()), () -> """ + Failed to discover parameter type supported by %s; \ + potentially caused by lacking parameterized type in class declaration.""".formatted( getClass().getName())); return typeBasedParameterResolverSuperclass.getActualTypeArguments()[0]; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java index 6225a7ddbf3b..4d358ceff2f5 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java @@ -60,10 +60,10 @@ public E get(String key, Function> lookup, E defaultVal catch (Exception ex) { // local copy necessary for use in lambda expression String constant = constantName; - logger.warn(() -> String.format( - "Invalid %s '%s' set via the '%s' configuration parameter. " - + "Falling back to the %s default value.", - enumDisplayName, constant, key, defaultValue.name())); + logger.warn(() -> """ + Invalid %s '%s' set via the '%s' configuration parameter. \ + Falling back to the %s default value.""".formatted(enumDisplayName, constant, key, + defaultValue.name())); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java index b11896fa039e..cb00c012d527 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java @@ -60,10 +60,9 @@ private Supplier> newInstanceSupplier(String className, String key) } private void logFailureMessage(String className, String key, Exception cause) { - logger.warn(cause, - () -> String.format("Failed to load default %s class '%s' set via the '%s' configuration parameter." - + " Falling back to default behavior.", - this.name, className, key)); + logger.warn(cause, () -> """ + Failed to load default %s class '%s' set via the '%s' configuration parameter. \ + Falling back to default behavior.""".formatted(this.name, className, key)); } private void logSuccessMessage(String className, String key) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java index 29fb3c54652d..e796a620b8a5 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java @@ -239,9 +239,9 @@ boolean mayReturnZeroContexts(ClassTemplateInvocationContextProvider provider, @Override protected String getZeroContextsProvidedErrorMessage(ClassTemplateInvocationContextProvider provider) { - return String.format( - "Provider [%s] did not provide any invocation contexts, but was expected to do so. " - + "You may override mayReturnZeroClassTemplateInvocationContexts() to allow this.", + return """ + Provider [%s] did not provide any invocation contexts, but was expected to do so. \ + You may override mayReturnZeroClassTemplateInvocationContexts() to allow this.""".formatted( provider.getClass().getSimpleName()); } 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 08fcb599dde0..6335844177d3 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 @@ -152,12 +152,11 @@ private static Extension readAndValidateExtensionFromField(Field field, @Nullabl declarativeExtensionTypes.forEach(extensionType -> { Class valueType = value.getClass(); - Preconditions.condition(!extensionType.equals(valueType), - () -> String.format( - "Failed to register extension via field [%s]. " - + "The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, " - + "but only one registration of a given extension type is permitted.", - field, valueType.getName())); + Preconditions.condition(!extensionType.equals(valueType), () -> """ + Failed to register extension via field [%s]. \ + The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, \ + but only one registration of a given extension type is permitted.""".formatted(field, + valueType.getName())); }); return (Extension) value; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index 32cbbcc8a4a2..7fc96df457ed 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -146,9 +146,9 @@ boolean mayReturnZeroContexts(TestTemplateInvocationContextProvider provider, @Override protected String getZeroContextsProvidedErrorMessage(TestTemplateInvocationContextProvider provider) { - return String.format( - "Provider [%s] did not provide any invocation contexts, but was expected to do so. " - + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this.", + return """ + Provider [%s] did not provide any invocation contexts, but was expected to do so. \ + You may override mayReturnZeroTestTemplateInvocationContexts() to allow this.""".formatted( provider.getClass().getSimpleName()); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java index bc69f0a0ce15..ad70aaa7913f 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -100,10 +100,10 @@ private Resolution resolve(Context context, List> enclosingClasses, Cla // @formatter:on if (matches.size() > 1) { Stream testDescriptors = matches.stream().map(Match::getTestDescriptor); - String message = String.format( - "Possible configuration error: method [%s] resulted in multiple TestDescriptors %s. " - + "This is typically the result of annotating a method with multiple competing annotations " - + "such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.", + String message = """ + Possible configuration error: method [%s] resulted in multiple TestDescriptors %s. \ + This is typically the result of annotating a method with multiple competing annotations \ + such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.""".formatted( method.toGenericString(), testDescriptors.map(d -> d.getClass().getName()).toList()); issueReporter.reportIssue( DiscoveryIssue.builder(Severity.WARNING, message).source(MethodSource.from(method))); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java index 7b3e27d6abc7..da3bb569f5d8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java @@ -100,10 +100,8 @@ private static boolean isCompatibleContainerType(Method method, DiscoveryIssueRe } private static DiscoveryIssue.Builder createTooGenericReturnTypeIssue(Method method) { - String message = String.format( - "The declared return type of @TestFactory method '%s' does not support static validation. It " - + EXPECTED_RETURN_TYPE_MESSAGE + ".", - method.toGenericString()); + String message = ("The declared return type of @TestFactory method '%s' does not support static validation. It " + + EXPECTED_RETURN_TYPE_MESSAGE + ".").formatted(method.toGenericString()); return DiscoveryIssue.builder(Severity.INFO, message) // .source(MethodSource.from(method)); } 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 6b8379a56f1f..d32ffde13468 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 @@ -188,16 +188,16 @@ private static void validateResolvedType(Parameter parameter, @Nullable Object v if (!isAssignableTo(value, type)) { String message; if (value == null && type.isPrimitive()) { - message = String.format( - "ParameterResolver [%s] resolved a null value for parameter [%s] " - + "in %s [%s], but a primitive of type [%s] is required.", + message = """ + ParameterResolver [%s] resolved a null value for parameter [%s] \ + in %s [%s], but a primitive of type [%s] is required.""".formatted( resolver.getClass().getName(), parameter, asLabel(executable), executable.toGenericString(), type.getName()); } else { - message = String.format( - "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] " - + "in %s [%s], but a value assignment compatible with [%s] is required.", + message = """ + ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] \ + in %s [%s], but a value assignment compatible with [%s] is required.""".formatted( resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameter, asLabel(executable), executable.toGenericString(), type.getName()); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java index 5217a5b73dac..ee11b5e004ef 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java @@ -68,8 +68,9 @@ private int failureThreshold(RepeatedTest repeatedTest, Method method) { if (failureThreshold != Integer.MAX_VALUE) { int repetitions = repeatedTest.value(); Preconditions.condition((failureThreshold > 0) && (failureThreshold < repetitions), - () -> String.format("Configuration error: @RepeatedTest on method [%s] must declare a " - + "'failureThreshold' greater than zero and less than the total number of repetitions [%d].", + () -> """ + Configuration error: @RepeatedTest on method [%s] must declare a \ + 'failureThreshold' greater than zero and less than the total number of repetitions [%d].""".formatted( method, repetitions)); } return failureThreshold; 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 a20c136fe73a..954099cbf1da 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 @@ -439,9 +439,9 @@ private boolean isLinkWithTargetOutsideTempDir(Path path) { private void warnAboutLinkWithTargetOutsideTempDir(String linkType, Path file) throws IOException { Path realPath = file.toRealPath(); - LOGGER.warn(() -> String.format( - "Deleting %s from location inside of temp dir (%s) " - + "to location outside of temp dir (%s) but not the target file/directory", + LOGGER.warn(() -> """ + Deleting %s from location inside of temp dir (%s) \ + to location outside of temp dir (%s) but not the target file/directory""".formatted( linkType, file, realPath)); } 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 135b4986a03c..0be381158c6a 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 @@ -86,10 +86,9 @@ private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration( return enumValue.get(); } else { - logger.warn(() -> String.format( - "Invalid ArgumentCountValidationMode '%s' set via the '%s' configuration parameter. " - + "Falling back to the %s default value.", - configValue, key, fallback.name())); + logger.warn(() -> """ + Invalid ArgumentCountValidationMode '%s' set via the '%s' configuration parameter. \ + Falling back to the %s default value.""".formatted(configValue, key, fallback.name())); return fallback; } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java index bed7e1b61a3b..41fa17bb11e6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java @@ -65,9 +65,9 @@ private static Constructor findBestConstructor(Class spiInterface, } } // Otherwise... - String message = String.format( - "Failed to find constructor for %s [%s]. " - + "Please ensure that a no-argument or a single constructor exists.", + String message = """ + Failed to find constructor for %s [%s]. \ + Please ensure that a no-argument or a single constructor exists.""".formatted( spiInterface.getSimpleName(), implementationClass.getName()); throw new JUnitException(message); } 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 9980fe7eefbf..d19eb232c616 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 @@ -143,10 +143,10 @@ private static ResolverFacade create(Executable executable, Annotation annotatio Preconditions.condition( aggregatorParameters.isEmpty() || aggregatorParameters.lastKey() == declaration.getParameterIndex() - 1, - () -> String.format( - "@%s %s declares formal parameters in an invalid order: " - + "argument aggregators must be declared after any indexed arguments " - + "and before any arguments resolved by another ParameterResolver.", + () -> """ + @%s %s declares formal parameters in an invalid order: \ + argument aggregators must be declared after any indexed arguments \ + and before any arguments resolved by another ParameterResolver.""".formatted( annotation.annotationType().getSimpleName(), DefaultParameterDeclarations.describe(executable))); aggregatorParameters.put(declaration.getParameterIndex(), declaration); 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 9dd598971388..1024d2fde94d 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 @@ -87,10 +87,9 @@ Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) @API(status = EXPERIMENTAL, since = "6.0") default @Nullable Object aggregateArguments(ArgumentsAccessor accessor, FieldContext context) throws ArgumentsAggregationException { - throw new JUnitException( - String.format("ArgumentsAggregator does not override the convert(ArgumentsAccessor, FieldContext) method. " - + "Please report this issue to the maintainers of %s.", - getClass().getName())); + throw new JUnitException(""" + ArgumentsAggregator does not override the convert(ArgumentsAccessor, FieldContext) method. \ + Please report this issue to the maintainers of %s.""".formatted(getClass().getName())); } } 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 16003b8b2c2f..a8ec1ec6c0a5 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 @@ -87,9 +87,8 @@ public interface ArgumentConverter { */ @API(status = EXPERIMENTAL, since = "6.0") 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.", - getClass().getName())); + throw new JUnitException(""" + ArgumentConverter does not override the convert(Object, FieldContext) method. \ + Please report this issue to the maintainers of %s.""".formatted(getClass().getName())); } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java index 307e2f4d930e..608a079c5db6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java @@ -72,10 +72,10 @@ public Stream provideArguments(ParameterDeclarations parame @Deprecated @API(status = DEPRECATED, since = "5.13") protected Stream provideArguments(ExtensionContext context, A annotation) { - throw new JUnitException(String.format( - "AnnotationBasedArgumentsProvider does not override the provideArguments(ParameterDeclarations, ExtensionContext, Annotation) method. " - + "Please report this issue to the maintainers of %s.", - getClass().getName())); + throw new JUnitException(""" + AnnotationBasedArgumentsProvider does not override the \ + provideArguments(ParameterDeclarations, ExtensionContext, Annotation) method. \ + Please report this issue to the maintainers of %s.""".formatted(getClass().getName())); } /** diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java index c7f24ed01038..ec362ffc7f46 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java @@ -79,10 +79,11 @@ default Stream provideArguments(ParameterDeclarations param return provideArguments(context); } catch (Exception e) { - throw new JUnitException(String.format( - "ArgumentsProvider does not override the provideArguments(ParameterDeclarations, ExtensionContext) method. " - + "Please report this issue to the maintainers of %s.", - getClass().getName()), e); + String message = """ + ArgumentsProvider does not override the provideArguments(ParameterDeclarations, ExtensionContext) method. \ + Please report this issue to the maintainers of %s.""".formatted( + getClass().getName()); + throw new JUnitException(message, e); } } 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 9f270d350387..0b591f49ef08 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 @@ -80,8 +80,7 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN List headers = useHeadersInDisplayName ? getHeaders(record) : List.of(); Preconditions.condition(!useHeadersInDisplayName || fields.size() <= headers.size(), // - () -> String.format( // - "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s", // + () -> "The number of columns (%d) exceeds the number of supplied headers (%d) in CSV record: %s".formatted( // fields.size(), headers.size(), fields)); // @Nullable diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 004982e7ab02..672bed2e24d5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -155,7 +155,7 @@ record DefaultFieldModifier(String emptyValue, Set nullValues, boolean i *

    * The marker is generated with a unique ID to ensure it cannot conflict with actual CSV content. */ - static final String NULL_MARKER = String.format("", UUID.randomUUID()); + static final String NULL_MARKER = "".formatted(UUID.randomUUID()); @Override public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean quoted, String field) { 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 eea09175a15a..1f80df4048f3 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.params.provider; -import static java.lang.String.format; import static java.util.Arrays.stream; import java.lang.reflect.Field; @@ -98,10 +97,10 @@ static Field findField(Class testClass, String fieldName) { 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; " - + "external @FieldSource fields must always be static.", - field.toGenericString())); + () -> """ + Field '%s' must be static: local @FieldSource fields must be static \ + unless the PER_CLASS @TestInstance lifecycle mode is used; \ + external @FieldSource fields must always be static.""".formatted(field.toGenericString())); return field; } 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 227fefc9aa17..fd7df78146c1 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.params.provider; -import static java.lang.String.format; import static java.util.Arrays.stream; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; @@ -180,10 +179,10 @@ private static boolean isTestMethod(Method candidate) { 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 " - + "unless the PER_CLASS @TestInstance lifecycle mode is used; " - + "external factory methods must always be static.", - factoryMethod.toGenericString())); + () -> """ + Method '%s' must be static: local factory methods must be static \ + unless the PER_CLASS @TestInstance lifecycle mode is used; \ + external factory methods must always be static.""".formatted(factoryMethod.toGenericString())); return factoryMethod; } 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 ecb187cdb48a..858417dd4083 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 @@ -10,7 +10,6 @@ package org.junit.platform.commons.util; -import static java.lang.String.format; import static java.util.Collections.synchronizedMap; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; @@ -645,7 +644,7 @@ public static Try tryToReadFieldValue(Class clazz, String fieldNa 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)); + () -> "Cannot read non-static field [%s] on a null instance.".formatted(field)); return Try.<@Nullable Object> call(() -> makeAccessible(field).get(instance)); } @@ -697,7 +696,7 @@ public static List readFieldValues(List fields, @Nullable Object insta 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())); + () -> "Cannot invoke non-static method [%s] on a null target.".formatted(method.toGenericString())); try { return makeAccessible(method).invoke(target, args); @@ -732,7 +731,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).getNonNullOrThrow( - cause -> new JUnitException(format("Could not load class [%s]", name), cause)); + cause -> new JUnitException("Could not load class [%s]".formatted(name), cause)); } /** @@ -891,7 +890,7 @@ public static String getFullyQualifiedMethodName(String className, String method Preconditions.notBlank(methodName, "Method name must not be null or blank"); Preconditions.notNull(parameterTypeNames, "Parameter type names must not be null"); - return String.format("%s#%s(%s)", className, methodName, parameterTypeNames); + return "%s#%s(%s)".formatted(className, methodName, parameterTypeNames); } /** @@ -1276,7 +1275,7 @@ public static Constructor getDeclaredConstructor(Class clazz) { .toArray(Constructor[]::new); Preconditions.condition(constructors.length == 1, - () -> String.format("Class [%s] must declare a single constructor", clazz.getName())); + () -> "Class [%s] must declare a single constructor".formatted(clazz.getName())); return (Constructor) constructors[0]; } @@ -1492,7 +1491,7 @@ private static Class loadRequiredParameterType(Class clazz, String methodN // @formatter:off return tryToLoadClass(typeName, classLoader) .getNonNullOrThrow(cause -> new JUnitException( - String.format("Failed to load parameter type [%s] for method [%s] in class [%s].", + "Failed to load parameter type [%s] for method [%s] in class [%s].".formatted( typeName, methodName, clazz.getName()), cause)); // @formatter:on } @@ -1558,8 +1557,8 @@ private static Optional findMethod(Class clazz, Predicate pre */ @API(status = STABLE, since = "1.7") public static Method getRequiredMethod(Class clazz, String methodName, Class... parameterTypes) { - return ReflectionUtils.findMethod(clazz, methodName, parameterTypes).orElseThrow( - () -> new JUnitException(format("Could not find method [%s] in class [%s]", methodName, clazz.getName()))); + return ReflectionUtils.findMethod(clazz, methodName, parameterTypes).orElseThrow(() -> new JUnitException( + "Could not find method [%s] in class [%s]".formatted(methodName, clazz.getName()))); } /** @@ -1964,7 +1963,7 @@ public enum CycleErrorHandling { THROW_EXCEPTION { @Override void handle(Class clazz, Class enclosing) { - throw new JUnitException(String.format("Detected cycle in inner class hierarchy between %s and %s", + throw new JUnitException("Detected cycle in inner class hierarchy between %s and %s".formatted( clazz.getName(), enclosing.getName())); } }, 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 cfe4ecb37a60..c9174ec09605 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 @@ -55,7 +55,7 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) { String msg = formatTestIdentifier(testIdentifier); String indentedReason = indented("Reason: %s".formatted(reason)); println(Style.SKIPPED, - String.format("%s" + STATUS_SEPARATOR + "SKIPPED%n" + INDENTATION + "%s", msg, indentedReason)); + ("%s" + STATUS_SEPARATOR + "SKIPPED%n" + INDENTATION + "%s").formatted(msg, indentedReason)); } } @@ -63,7 +63,7 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) { public void executionStarted(TestIdentifier testIdentifier) { if (shouldPrint(testIdentifier)) { String msg = formatTestIdentifier(testIdentifier); - println(Style.NONE, String.format("%s" + STATUS_SEPARATOR + "STARTED", msg)); + println(Style.NONE, ("%s" + STATUS_SEPARATOR + "STARTED").formatted(msg)); } } @@ -75,13 +75,12 @@ public void executionFinished(TestIdentifier testIdentifier, TestExecutionResult String msg = formatTestIdentifier(testIdentifier); Throwable throwable = testExecutionResult.getThrowable().get(); String stacktrace = indented(ExceptionUtils.readStackTrace(throwable)); - println(style, - String.format("%s" + STATUS_SEPARATOR + "%s%n" + INDENTATION + "%s", msg, status, stacktrace)); + println(style, ("%s" + STATUS_SEPARATOR + "%s%n" + INDENTATION + "%s").formatted(msg, status, stacktrace)); } else if (shouldPrint(testIdentifier) || testExecutionResult.getStatus() != SUCCESSFUL) { Style style = Style.valueOf(testExecutionResult); String msg = formatTestIdentifier(testIdentifier); - println(style, String.format("%s" + STATUS_SEPARATOR + "%s", msg, status)); + println(style, ("%s" + STATUS_SEPARATOR + "%s").formatted(msg, status)); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java index 103621853033..a2e7de6ebb93 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java @@ -174,7 +174,7 @@ private void printDetail(Style style, String detail, String format, Object... ar String detailFormat = "%9s"; // omit detail string if it's empty if (!detail.isEmpty()) { - printf(NONE, "%s", String.format(detailFormat + ": ", detail)); + printf(NONE, "%s", (detailFormat + ": ").formatted(detail)); } // trivial case: at least one arg is given? Let printf do the entire work if (args.length > 0) { @@ -185,7 +185,7 @@ private void printDetail(Style style, String detail, String format, Object... ar String[] lines = format.split("\\R"); printf(style, "%s", lines[0]); if (lines.length > 1) { - String delimiter = System.lineSeparator() + verticals + String.format(detailFormat + " ", ""); + String delimiter = System.lineSeparator() + verticals + (detailFormat + " ").formatted(""); for (int i = 1; i < lines.length; i++) { printf(NONE, "%s", delimiter); printf(style, "%s", lines[i]); 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 1f09383680fd..34daa89c156e 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 @@ -125,12 +125,12 @@ public void execute() { // is cleared for reuse of the thread in subsequent task executions. // See https://github.com/junit-team/junit-framework/issues/1688 if (Thread.interrupted()) { - logger.debug(() -> String.format( - "Execution of TestDescriptor with display name [%s] " - + "and unique ID [%s] failed to clear the 'interrupted status' flag for the " - + "current thread. JUnit has cleared the flag, but you may wish to investigate " - + "why the flag was not cleared by user code.", - this.testDescriptor.getDisplayName(), this.testDescriptor.getUniqueId())); + logger.debug(() -> """ + Execution of TestDescriptor with display name [%s] \ + and unique ID [%s] failed to clear the 'interrupted status' flag for the \ + current thread. JUnit has cleared the flag, but you may wish to investigate \ + why the flag was not cleared by user code.""".formatted(this.testDescriptor.getDisplayName(), + this.testDescriptor.getUniqueId())); } finalizer.run(); } 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 7714d3fb7618..9ec2d1f09abb 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 @@ -137,10 +137,10 @@ private Severity getCriticalSeverity() { return Severity.valueOf(value.toUpperCase(Locale.ROOT)); } catch (Exception e) { - logger.warn(() -> String.format( - "Invalid DiscoveryIssue.Severity '%s' set via the '%s' configuration parameter. " - + "Falling back to the %s default value.", - value, LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, defaultValue)); + logger.warn(() -> """ + Invalid DiscoveryIssue.Severity '%s' set via the '%s' configuration parameter. \ + Falling back to the %s default value.""".formatted(value, + LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, defaultValue)); return defaultValue; } }) // 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 9f589354ae5c..ff3cc6a8b0cd 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 @@ -202,9 +202,9 @@ void throwsExceptionWhenArgumentsProviderDoesNotContainUnambiguousConstructor() String className = AmbiguousConstructorArgumentsProvider.class.getName(); assertThat(exception) // - .hasMessage(String.format("Failed to find constructor for ArgumentsProvider [%s]. " - + "Please ensure that a no-argument or a single constructor exists.", - className)); + .hasMessage(""" + Failed to find constructor for ArgumentsProvider [%s]. \ + Please ensure that a no-argument or a single constructor exists.""", className); } private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { From fae5a6d3f1256a5a8c0955cd5c7c4785de8ec72f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 10:15:44 +0200 Subject: [PATCH 060/162] Hide utility class constructors --- documentation/src/main/java/example/util/StringUtils.java | 3 +++ .../src/tools/java/org/junit/api/tools/ApiReportGenerator.java | 3 +++ gradle/config/checkstyle/checkstyleMain.xml | 1 + .../java/org/junit/jupiter/api/AssertTimeoutPreemptively.java | 3 +++ .../main/java/org/junit/jupiter/api/RandomOrdererUtils.java | 3 +++ .../org/junit/jupiter/engine/descriptor/CallbackSupport.java | 3 +++ .../org/junit/jupiter/engine/descriptor/DisplayNameUtils.java | 3 +++ .../junit/jupiter/engine/descriptor/MethodSourceSupport.java | 3 +++ .../jupiter/engine/discovery/DiscoverySelectorResolver.java | 3 +++ .../jupiter/engine/execution/ParameterResolutionUtils.java | 3 +++ .../engine/support/JupiterThrowableCollectorFactory.java | 3 +++ .../junit/jupiter/engine/support/MethodReflectionUtils.java | 3 +++ .../junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java | 3 +++ .../junit/jupiter/params/ParameterizedTestSpiInstantiator.java | 3 +++ .../org/junit/jupiter/params/provider/CsvReaderFactory.java | 3 +++ .../platform/commons/support/scanning/ClasspathFilters.java | 3 +++ .../junit/platform/commons/util/ClasspathScannerLoader.java | 3 +++ .../org/junit/platform/commons/util/KotlinReflectionUtils.java | 3 +++ .../platform/commons/util/KotlinSuspendingFunctionUtils.java | 3 +++ .../main/java/org/junit/platform/commons/util/ModuleUtils.java | 3 +++ .../junit/platform/commons/test/ConcurrencyTestingUtils.java | 3 +++ .../java/org/junit/platform/commons/test/IdeUtils.java | 3 +++ .../main/java/org/junit/platform/console/ConsoleLauncher.java | 3 +++ .../java/org/junit/platform/console/options/ConsoleUtils.java | 3 +++ .../junit/platform/console/tasks/DiscoveryRequestCreator.java | 3 +++ .../engine/discovery/DiscoverySelectorIdentifierParsers.java | 3 +++ .../junit/platform/engine/support/discovery/ResourceUtils.java | 3 +++ .../java/org/junit/platform/fakes/FaultyTestEngines.java | 3 +++ .../platform/launcher/core/ClasspathAlignmentChecker.java | 3 +++ .../junit/platform/launcher/core/ServiceLoaderRegistry.java | 3 +++ .../main/java/org/junit/platform/launcher/jfr/JfrUtils.java | 3 +++ .../junit/platform/launcher/tagexpression/TagExpressions.java | 3 +++ .../launcher/core/LauncherFactoryForTestingPurposesOnly.java | 3 +++ .../launcher/core/NamespacedHierarchicalStoreProviders.java | 3 +++ .../junit/platform/launcher/core/OutputDirectoryProviders.java | 3 +++ .../java/org/junit/platform/reporting/testutil/FileUtils.java | 3 +++ .../platform/suite/engine/AdditionalDiscoverySelectors.java | 3 +++ .../java/org/junit/platform/testkit/engine/Assertions.java | 3 +++ .../main/java/org/junit/vintage/engine/JUnit4VersionCheck.java | 3 +++ .../src/main/java/platform/tooling/support/Helper.java | 3 +++ .../main/java/platform/tooling/support/ProcessStarters.java | 3 +++ 41 files changed, 121 insertions(+) diff --git a/documentation/src/main/java/example/util/StringUtils.java b/documentation/src/main/java/example/util/StringUtils.java index b0eee5e142f8..f2443b1addb4 100644 --- a/documentation/src/main/java/example/util/StringUtils.java +++ b/documentation/src/main/java/example/util/StringUtils.java @@ -26,4 +26,7 @@ public static boolean isPalindrome(@Nullable String candidate) { return true; } + private StringUtils() { + } + } diff --git a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java index 1cf2308ed574..5aff637da94e 100644 --- a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java +++ b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java @@ -176,4 +176,7 @@ private static Stream collectMethods(ScanResult scanResult) { .filter(m -> m.getAnnotationInfo(API.class) != null); } + private ApiReportGenerator() { + } + } diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index 5428e9d1e413..2e1a94a97c0d 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -34,6 +34,7 @@ + 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 a2d277de1932..3f1009f56ec9 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 @@ -166,4 +166,7 @@ public Thread newThread(Runnable r) { } } + private AssertTimeoutPreemptively() { + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java index 4968973718e1..66189bbf0a9f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java @@ -49,4 +49,7 @@ private static Optional getCustomSeed(Function> c } }); } + + private RandomOrdererUtils() { + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/CallbackSupport.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/CallbackSupport.java index c7c6cf0e34bf..428d51a0df5b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/CallbackSupport.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/CallbackSupport.java @@ -56,4 +56,7 @@ protected interface CallbackInvoker { } + private CallbackSupport() { + } + } 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 beaf49db101c..2a9552015cb8 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 @@ -155,4 +155,7 @@ private static Optional findDisplayNameGenerator(List Constructor findBestConstructor(Class spiInterface, throw new JUnitException(message); } + private ParameterizedTestSpiInstantiator() { + } + } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 672bed2e24d5..00f6c6de2ddf 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -174,4 +174,7 @@ public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean } + private CsvReaderFactory() { + } + } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java index 0ce211f5f924..0505d7e0f3f6 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java @@ -42,4 +42,7 @@ private static boolean isClassFile(Path file) { return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX); } + private ClasspathFilters() { + } + } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java index cfa60d58369b..e11d06e885da 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java @@ -43,4 +43,7 @@ static ClasspathScanner getInstance() { return new DefaultClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); } + private ClasspathScannerLoader() { + } + } 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 f27ba3c1907f..42e5edb1073e 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 @@ -167,4 +167,7 @@ private static void requireDependency(Method method, boolean condition, String d dependencyNotation)); } + private KotlinReflectionUtils() { + } + } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java index f2b186aa575f..4e13f95e8b39 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinSuspendingFunctionUtils.java @@ -110,4 +110,7 @@ private static KFunction getKotlinFunction(Method method) { return Preconditions.notNull(ReflectJvmMapping.getKotlinFunction(method), () -> "Failed to get Kotlin function for method: " + method); } + + private KotlinSuspendingFunctionUtils() { + } } 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 40208636bd4c..30d74df6ff36 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 @@ -307,4 +307,7 @@ private Resource loadResourceUnchecked(String binaryName) { } + private ModuleUtils() { + } + } 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 49818e769298..057dc98d986a 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 @@ -70,4 +70,7 @@ public static void executeConcurrently(int threads, Runnable action) throws Exce } } } + + private ConcurrencyTestingUtils() { + } } diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/IdeUtils.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/IdeUtils.java index 1b20c64c8113..153cfba3e205 100644 --- a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/IdeUtils.java +++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/IdeUtils.java @@ -24,4 +24,7 @@ public static boolean runningInEclipse() { stream -> stream.anyMatch(stackFrame -> stackFrame.getClassName().startsWith("org.eclipse.jdt"))); } + private IdeUtils() { + } + } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java b/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java index 929ec8c035fe..6232d7d5c5b7 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java @@ -47,4 +47,7 @@ private static CommandFacade newCommandFacade(CustomClassLoaderCloseStrategy cla outputOptions, classLoaderCleanupStrategy)); } + private ConsoleLauncher() { + } + } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java index d470acbd7963..e0a33bc99d0c 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java @@ -41,4 +41,7 @@ public static Charset charset() { return console != null ? console.charset() : Charset.defaultCharset(); } + private ConsoleUtils() { + } + } 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 fd687ba632be..67a74a9179b7 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 @@ -156,4 +156,7 @@ private static ClassNameFilter includedClassNamePatterns(TestDiscoveryOptions op return includeClassNamePatterns(patternStreams.toArray(String[]::new)); } + private DiscoveryRequestCreator() { + } + } 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 f844100d44d6..07d467c32ae0 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 @@ -83,4 +83,7 @@ private enum Singleton { } + private DiscoverySelectorIdentifierParsers() { + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java index 127b2503952d..6a89461d3d1b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java @@ -45,4 +45,7 @@ private static String packageName(String classpathResourceName) { String resourcePackagePath = classpathResourceName.substring(0, lastIndexOf); return resourcePackagePath.replace(CLASSPATH_RESOURCE_PATH_SEPARATOR, PACKAGE_SEPARATOR_CHAR); } + + private ResourceUtils() { + } } diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/FaultyTestEngines.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/FaultyTestEngines.java index 9199a1388af3..86c72353dd16 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/FaultyTestEngines.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/FaultyTestEngines.java @@ -60,4 +60,7 @@ public void execute(ExecutionRequest request) { } }; } + + private FaultyTestEngines() { + } } 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 5511b56db1fe..9f3fabe91689 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 @@ -85,4 +85,7 @@ static Optional check(LinkageError error, Function dummyNamespacedHierarchical public static NamespacedHierarchicalStore dummyNamespacedHierarchicalStoreWithNoParent() { return new NamespacedHierarchicalStore<>(null, closeAutoCloseables()); } + + private NamespacedHierarchicalStoreProviders() { + } } diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java index b4d30b2b7b8f..c277b9f89fc3 100644 --- a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java @@ -26,4 +26,7 @@ public static OutputDirectoryProvider dummyOutputDirectoryProvider() { public static OutputDirectoryProvider hierarchicalOutputDirectoryProvider(Path rootDir) { return new HierarchicalOutputDirectoryProvider(() -> rootDir); } + + private OutputDirectoryProviders() { + } } diff --git a/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java index 04228a9aa1fb..9d21c5d85d98 100644 --- a/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java +++ b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java @@ -28,4 +28,7 @@ public static Path findPath(Path rootDir, String syntaxAndPattern) { throw new UncheckedIOException(e); } } + + private FileUtils() { + } } diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java index 8def4993f7c8..2c00b7f90c85 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/AdditionalDiscoverySelectors.java @@ -122,4 +122,7 @@ private static Stream uniqueStreamOf(T[] elements) { return Arrays.stream(elements).distinct(); } + private AdditionalDiscoverySelectors() { + } + } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java index 2f7d3e157304..993b3425100d 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java @@ -84,4 +84,7 @@ private static String formatValues(long expected, long actual) { return "expected: <%d> but was: <%d>".formatted(expected, actual); } + private Assertions() { + } + } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java index f27eaef647ca..08d9599cdd47 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java @@ -74,4 +74,7 @@ private static String readVersion(Supplier versionSupplier) { } } + private JUnit4VersionCheck() { + } + } diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java index 0df95dd15ca8..42d279a2db49 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java @@ -77,4 +77,7 @@ public static Optional getJavaHomeWithNativeImageSupport(int version) { var value = System.getProperty("java.home." + version + ".nativeImage"); return Optional.ofNullable(value).map(Path::of); } + + private Helper() { + } } diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java index 94df6091b784..0fc656e669c8 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java @@ -73,4 +73,7 @@ public static Optional getGradleJavaHome() { public static int getGradleJavaVersion() { return Integer.parseInt(System.getProperty("gradle.java.version")); } + + private ProcessStarters() { + } } From 1536aa39502ccd229c5ef51aa90adf2c51a0491c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 10:16:05 +0200 Subject: [PATCH 061/162] Order modifiers canonically --- gradle/config/checkstyle/checkstyleMain.xml | 1 + .../jupiter/engine/extension/TimeoutInvocationFactory.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index 2e1a94a97c0d..3b9e260ce2fd 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -35,6 +35,7 @@ + diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java index 290da23062d0..daa4853550ef 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java @@ -54,7 +54,7 @@ private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { } @SuppressWarnings({ "deprecation", "try" }) - private static abstract class ExecutorResource implements Store.CloseableResource, AutoCloseable { + private abstract static class ExecutorResource implements Store.CloseableResource, AutoCloseable { protected final ScheduledExecutorService executor; From aca30c5b64805a424b6639ec9ceecc23a5cd5c10 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 10:16:50 +0200 Subject: [PATCH 062/162] Avoid potential NPEs for `String.equals()` calls --- gradle/config/checkstyle/checkstyleMain.xml | 1 + .../jupiter/params/ParameterizedInvocationNameFormatter.java | 2 +- .../java/org/junit/platform/commons/util/AnnotationUtils.java | 2 +- .../java/org/junit/platform/commons/util/ModuleUtils.java | 2 +- .../src/main/java/platform/tooling/support/Helper.java | 4 ++-- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index 3b9e260ce2fd..2cea590af94b 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -36,6 +36,7 @@ + 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 d459709fde13..beebae5a8a34 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 @@ -57,7 +57,7 @@ static ParameterizedInvocationNameFormatter create(ExtensionContext extensionCon ParameterizedDeclarationContext declarationContext) { String name = declarationContext.getDisplayNamePattern(); - String pattern = name.equals(DEFAULT_DISPLAY_NAME) + String pattern = DEFAULT_DISPLAY_NAME.equals(name) ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY) // .orElse(DEFAULT_DISPLAY_NAME_PATTERN) : name; 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 d277c250cfd4..fcf4ece0978a 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 @@ -381,7 +381,7 @@ private static boolean isRepeatableAnnotationContainer(Class { // @formatter:off Repeatable repeatable = Arrays.stream(candidate.getMethods()) - .filter(attribute -> attribute.getName().equals("value") && attribute.getReturnType().isArray()) + .filter(attribute -> "value".equals(attribute.getName()) && attribute.getReturnType().isArray()) .findFirst() .map(attribute -> attribute.getReturnType().getComponentType().getAnnotation(Repeatable.class)) .orElse(null); 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 30d74df6ff36..c65e8e8dda3b 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 @@ -223,7 +223,7 @@ List> scan(ModuleReference reference) { // @formatter:off return names.filter(name -> name.endsWith(".class")) .map(this::className) - .filter(name -> !name.equals("module-info")) + .filter(name -> !"module-info".equals(name)) .filter(classFilter::match) .> map(this::loadClassUnchecked) .filter(classFilter::match) diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java index 42d279a2db49..af7379ebeb53 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java @@ -51,8 +51,8 @@ public static List loadModuleDirectoryNames() { .filter(Matcher::matches) // .map(matcher -> matcher.group(1)) // .filter(name -> name.startsWith("junit-")) // - .filter(name -> !name.equals("junit-bom")) // - .filter(name -> !name.equals("junit-platform-console-standalone")).toList(); + .filter(name -> !"junit-bom".equals(name)) // + .filter(name -> !"junit-platform-console-standalone".equals(name)).toList(); } catch (Exception e) { throw new AssertionError("loading module directory names failed: " + SETTINGS_GRADLE); From c79a62dff2cda1851f7524ae6dc8f204509e8125 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 10:17:18 +0200 Subject: [PATCH 063/162] Declare `serialVersionUID` explicitly --- .../ForkJoinPoolHierarchicalTestExecutorService.java | 4 ++++ 1 file changed, 4 insertions(+) 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 ced88fc1b946..f1e08f97816e 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 @@ -16,6 +16,7 @@ import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; +import java.io.Serial; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Constructor; import java.util.ArrayDeque; @@ -223,6 +224,9 @@ public void close() { @SuppressWarnings({ "serial", "RedundantSuppression" }) class ExclusiveTask extends ForkJoinTask { + @Serial + private static final long serialVersionUID = 1; + private final TestTask testTask; ExclusiveTask(TestTask testTask) { From a3d2a2b28e32a0636aee7f3b3b9c8d69a84a5a50 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 10:52:49 +0200 Subject: [PATCH 064/162] Remove empty statement --- gradle/config/checkstyle/checkstyleMain.xml | 1 + .../main/java/org/junit/platform/engine/ExecutionRequest.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index 2cea590af94b..eecf914bb4d4 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -37,6 +37,7 @@ + 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 cc0a671a0eb0..16afe7fae878 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 @@ -67,7 +67,6 @@ private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListe this.outputDirectoryProvider = outputDirectoryProvider; this.requestLevelStore = requestLevelStore; this.cancellationToken = Preconditions.notNull(cancellationToken, "cancellationToken must not be null"); - ; } /** From eaf5ae996cf3638f88667da8d253d7018c513b24 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 14:50:50 +0000 Subject: [PATCH 065/162] Allow `Stream` return types (#4750) For consistency with `ClassTemplateInvocationContextProvider` and to make implementations simpler, this commit changes the return type of the `provideTestTemplateInvocationContexts(ExtensionContext)` method of the `TestTemplateInvocationContextProvider` interface to allow more concrete `Streams` to be returned. This change is source compatible, meaning implementations will still compile when declaring `Stream` as method return type. To check for binary compatibility, I used Pioneer's `RetryingTestExtension` with a 6.0.0-SNAPSHOT that included this change. The extension kept working. Nevertheless, I included this change as potentially breaking in the release notes. Resolves #3577. --- .../docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc | 5 ++++- .../api/extension/TestTemplateInvocationContextProvider.java | 2 +- .../jupiter/engine/extension/RepeatedTestExtension.java | 3 +-- .../org/junit/jupiter/params/ParameterizedClassContext.java | 5 ++--- .../junit/jupiter/params/ParameterizedClassExtension.java | 5 ++--- .../org/junit/jupiter/params/ParameterizedTestContext.java | 5 ++--- .../org/junit/jupiter/params/ParameterizedTestExtension.java | 5 ++--- 7 files changed, 14 insertions(+), 16 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 91ccc894b9d3..246a3ac4c8d4 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -61,7 +61,10 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes -* ❓ +* Change return type of `provideTestTemplateInvocationContexts(ExtensionContext)` method + of the `TestTemplateInvocationContextProvider` interface from + `Stream` to + `Stream`. [[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java index 90cb4b79f033..4fbb8ee215c0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java @@ -91,7 +91,7 @@ public interface TestTemplateInvocationContextProvider extends Extension { * @see #supportsTestTemplate * @see ExtensionContext */ - Stream provideTestTemplateInvocationContexts(ExtensionContext context); + Stream provideTestTemplateInvocationContexts(ExtensionContext context); /** * Signal that this provider may provide zero diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java index ee11b5e004ef..8ca5f9509a3a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.platform.commons.util.Preconditions; @@ -38,7 +37,7 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); String displayName = context.getDisplayName(); RepeatedTest repeatedTest = findAnnotation(testMethod, RepeatedTest.class).get(); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java index b55964af555b..760291dd8c59 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java @@ -28,13 +28,12 @@ import java.util.function.Predicate; import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.HierarchyTraversalMode; import org.junit.platform.commons.util.ReflectionUtils; -class ParameterizedClassContext implements ParameterizedDeclarationContext { +class ParameterizedClassContext implements ParameterizedDeclarationContext { private final Class testClass; private final ParameterizedClass annotation; @@ -124,7 +123,7 @@ public ResolverFacade getResolverFacade() { } @Override - public ClassTemplateInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, + public ParameterizedClassInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { return new ParameterizedClassInvocationContext(this, formatter, arguments, invocationIndex); } 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 3f134156eba6..0fe4cdc4d063 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 @@ -21,7 +21,6 @@ import java.util.stream.Stream; import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ClassTemplateInvocationContext; import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; @@ -35,7 +34,7 @@ /** * @since 5.13 */ -class ParameterizedClassExtension extends ParameterizedInvocationContextProvider +class ParameterizedClassExtension extends ParameterizedInvocationContextProvider implements ClassTemplateInvocationContextProvider, ParameterResolver { private static final String DECLARATION_CONTEXT_KEY = "context"; @@ -70,7 +69,7 @@ public boolean supportsClassTemplate(ExtensionContext extensionContext) { } @Override - public Stream provideClassTemplateInvocationContexts( + public Stream provideClassTemplateInvocationContexts( ExtensionContext extensionContext) { return provideInvocationContexts(extensionContext, getDeclarationContext(extensionContext)); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java index 50a9417b3a11..aecbe3c57de6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java @@ -12,7 +12,6 @@ import java.lang.reflect.Method; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.platform.commons.util.Preconditions; @@ -22,7 +21,7 @@ * * @since 5.3 */ -class ParameterizedTestContext implements ParameterizedDeclarationContext { +class ParameterizedTestContext implements ParameterizedDeclarationContext { private final Class testClass; private final Method method; @@ -77,7 +76,7 @@ public ResolverFacade getResolverFacade() { } @Override - public TestTemplateInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, + public ParameterizedTestInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, Arguments arguments, int invocationIndex) { return new ParameterizedTestInvocationContext(this, formatter, arguments, 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 31de4ad836aa..08ea81f7cf21 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 @@ -18,13 +18,12 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; /** * @since 5.0 */ -class ParameterizedTestExtension extends ParameterizedInvocationContextProvider +class ParameterizedTestExtension extends ParameterizedInvocationContextProvider implements TestTemplateInvocationContextProvider { static final String DECLARATION_CONTEXT_KEY = "context"; @@ -45,7 +44,7 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts( + public Stream provideTestTemplateInvocationContexts( ExtensionContext extensionContext) { return provideInvocationContexts(extensionContext, getDeclarationContext(extensionContext)); From 2346c991e645a81e65f2356b786c4be32d7a5188 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 11 Jul 2025 20:08:22 +0200 Subject: [PATCH 066/162] Fix typo --- .../kotlin/junitbuild.java-nullability-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts index ddb8f9f67b40..98e5b3c1a996 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -36,7 +36,7 @@ tasks.withType().configureEach { "InlineMeSuggester", "ImmutableEnumChecker", - // Resolving findings for this checks requires using Guava which we don't want to use + // Resolving findings for this check requires using Guava which we don't want to use "StringSplitter", // Produces a lot of findings that we consider to be false positives, for example for package-private From 3c42e165156a92ebd8cda778aafe07873182c3b5 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 11 Jul 2025 20:19:44 +0200 Subject: [PATCH 067/162] Use IETF BCP 47 language tag format to convert from `String` to `Locale` Related issue: #853 --- .../release-notes/release-notes-6.0.0-M2.adoc | 7 +++ .../asciidoc/user-guide/writing-tests.adoc | 6 --- .../config/DefaultJupiterConfiguration.java | 11 +++-- .../ParameterizedInvocationContext.java | 2 +- .../junit/jupiter/params/ResolverFacade.java | 6 +-- .../aggregator/DefaultArgumentsAccessor.java | 8 ++-- .../converter/DefaultArgumentConverter.java | 47 ++----------------- .../StringToCommonJavaTypesConverter.java | 3 +- .../engine/extension/TempDirectoryTests.java | 5 +- .../ParameterizedTestIntegrationTests.java | 37 +++++++++++---- .../DefaultArgumentsAccessorTests.java | 2 +- .../DefaultArgumentConverterTests.java | 43 +---------------- .../ArgumentsAccessorKotlinTests.kt | 2 +- .../conversion/ConversionSupportTests.java | 3 +- 14 files changed, 59 insertions(+), 123 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 246a3ac4c8d4..1e78b05dca56 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -26,6 +26,9 @@ repository on GitHub. * Deprecate `Launcher.execute(TestPlan, TestExecutionListener[])` and `Launcher.execute(LauncherDiscoveryRequest, TestExecutionListener[])` in favor of `Launcher.execute(LauncherExecutionRequest)` +* `ConversionSupport` now converts `String` to `Locale` using the IETF BCP 47 language tag + format supported by the `Locale.forLanguageTag(String)` factory method instead of the + format used by the deprecated `Locale(String)` constructor. [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements @@ -65,6 +68,10 @@ repository on GitHub. of the `TestTemplateInvocationContextProvider` interface from `Stream` to `Stream`. +* Remove support for `junit.jupiter.params.arguments.conversion.locale.format` + configuration parameter. `Locale` conversions are now always performed using the IETF + BCP 47 language tag format supported by the `Locale.forLanguageTag(String)` factory + method. [[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 41555bc48620..7340e0ac5b3e 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -2500,12 +2500,6 @@ integral types: `byte`, `short`, `int`, `long`, and their boxed counterparts. | `java.util.UUID` | `"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"` -> `UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")` |=== -WARNING: To revert to the old `java.util.Locale` conversion behavior of version 5.12 and -earlier (which called the deprecated `Locale(String)` constructor), you can set the -`junit.jupiter.params.arguments.conversion.locale.format` -<> to `iso_639`. However, please -note that this parameter is deprecated and will be removed in a future release. - [[writing-tests-parameterized-tests-argument-conversion-implicit-fallback]] ====== Fallback String-to-Object Conversion diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 3b0ba96093b2..78bd4e5705e4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -48,7 +48,10 @@ @API(status = INTERNAL, since = "5.4") public class DefaultJupiterConfiguration implements JupiterConfiguration { - private static final List UNSUPPORTED_CONFIGURATION_PARAMETERS = List.of("junit.jupiter.tempdir.scope"); + private static final List UNSUPPORTED_CONFIGURATION_PARAMETERS = List.of( // + "junit.jupiter.tempdir.scope", // + "junit.jupiter.params.arguments.conversion.locale.format" // + ); private static final EnumConfigurationParameterConverter executionModeConverter = // new EnumConfigurationParameterConverter<>(ExecutionMode.class, "parallel execution mode"); @@ -88,9 +91,9 @@ public DefaultJupiterConfiguration(ConfigurationParameters configurationParamete private void validateConfigurationParameters(DiscoveryIssueReporter issueReporter) { UNSUPPORTED_CONFIGURATION_PARAMETERS.forEach(key -> configurationParameters.get(key) // .ifPresent(value -> { - var warning = DiscoveryIssue.create(Severity.WARNING, - "The '%s' configuration parameter is no longer supported: %s. Please remove it from your configuration.".formatted( - key, value)); + var warning = DiscoveryIssue.create(Severity.WARNING, """ + The '%s' configuration parameter is no longer supported. \ + Please remove it from your configuration.""".formatted(key)); issueReporter.reportIssue(warning); })); } 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 01e1d1e7f6a7..ce9e38931655 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 @@ -74,7 +74,7 @@ private void storeParameterInfo(ExtensionContext context) { ClassLoader classLoader = getClassLoader(this.declarationContext.getTestClass()); @Nullable Object[] arguments = this.arguments.getConsumedPayloads(); - ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(context, invocationIndex, classLoader, arguments); + ArgumentsAccessor accessor = DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments); new DefaultParameterInfo(declarations, accessor).store(context); } 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 d19eb232c616..3495015a73bb 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 @@ -435,7 +435,7 @@ private static Converter createConverter(ParameterDeclaration declaration, Exten .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentConverter.class, clazz, extensionContext)) .map(converter -> AnnotationConsumerInitializer.initialize(declaration.getAnnotatedElement(), converter)) .map(Converter::new) - .orElseGet(() -> Converter.createDefault(extensionContext)); + .orElse(Converter.DEFAULT); } // @formatter:on catch (Exception ex) { throw parameterResolutionException("Error creating ArgumentConverter", ex, declaration.getParameterIndex()); @@ -479,9 +479,7 @@ Object resolve(FieldContext fieldContext, ExtensionContext extensionContext, Eva private record Converter(ArgumentConverter argumentConverter) implements Resolver { - private static Converter createDefault(ExtensionContext context) { - return new Converter(new DefaultArgumentConverter(context)); - } + static final Converter DEFAULT = new Converter(DefaultArgumentConverter.INSTANCE); @Override public @Nullable Object resolve(ParameterContext parameterContext, int parameterIndex, 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 80376859b3da..0128da8fd153 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 @@ -19,7 +19,6 @@ 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; import org.junit.platform.commons.util.Preconditions; @@ -41,13 +40,12 @@ public class DefaultArgumentsAccessor implements ArgumentsAccessor { private final @Nullable Object[] arguments; private final BiFunction<@Nullable Object, Class, @Nullable Object> converter; - public static DefaultArgumentsAccessor create(ExtensionContext context, int invocationIndex, - ClassLoader classLoader, @Nullable Object[] arguments) { + public static DefaultArgumentsAccessor create(int invocationIndex, ClassLoader classLoader, + @Nullable Object[] arguments) { Preconditions.notNull(classLoader, "ClassLoader must not be null"); BiFunction<@Nullable Object, Class, @Nullable Object> converter = (source, - targetType) -> new DefaultArgumentConverter(context) // - .convert(source, targetType, classLoader); + targetType) -> DefaultArgumentConverter.INSTANCE.convert(source, targetType, classLoader); return new DefaultArgumentsAccessor(converter, invocationIndex, arguments); } 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 d8ed795f80e2..260979b8de63 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 @@ -21,11 +21,9 @@ import java.util.Currency; import java.util.Locale; import java.util.UUID; -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; import org.junit.platform.commons.support.conversion.ConversionException; @@ -53,31 +51,9 @@ @API(status = INTERNAL, since = "5.0") public class DefaultArgumentConverter implements ArgumentConverter { - /** - * Property name used to set the format for the conversion of {@link Locale} - * arguments: {@value} - * - *

    Supported Values

    - *
      - *
    • {@code bcp_47}: uses the IETF BCP 47 language tag format, delegating - * the conversion to {@link Locale#forLanguageTag(String)}
    • - *
    • {@code iso_639}: uses the ISO 639 alpha-2 or alpha-3 language code - * format, delegating the conversion to {@link Locale#Locale(String)}
    • - *
    - * - *

    If not specified, the default is {@code bcp_47}. - * - * @since 5.13 - */ - public static final String DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME = "junit.jupiter.params.arguments.conversion.locale.format"; - - private static final Function TRANSFORMER = value -> LocaleConversionFormat.valueOf( - value.strip().toUpperCase(Locale.ROOT)); - - private final ExtensionContext context; - - public DefaultArgumentConverter(ExtensionContext context) { - this.context = context; + public static final DefaultArgumentConverter INSTANCE = new DefaultArgumentConverter(); + + private DefaultArgumentConverter() { } @Override @@ -110,10 +86,6 @@ public DefaultArgumentConverter(ExtensionContext context) { } if (source instanceof String string) { - if (targetType == Locale.class && getLocaleConversionFormat() == LocaleConversionFormat.BCP_47) { - return Locale.forLanguageTag(string); - } - try { return convert(string, targetType, classLoader); } @@ -126,22 +98,9 @@ public DefaultArgumentConverter(ExtensionContext context) { source.getClass().getTypeName(), targetType.getTypeName())); } - private LocaleConversionFormat getLocaleConversionFormat() { - return context.getConfigurationParameter(DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME, TRANSFORMER) // - .orElse(LocaleConversionFormat.BCP_47); - } - @Nullable Object convert(@Nullable String source, Class targetType, ClassLoader classLoader) { return ConversionSupport.convert(source, targetType, classLoader); } - enum LocaleConversionFormat { - - BCP_47, - - ISO_639 - - } - } 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 7eb2bd31bd16..36f1eee73ec3 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 @@ -27,7 +27,6 @@ class StringToCommonJavaTypesConverter implements StringToObjectConverter { - @SuppressWarnings("deprecation") private static final Map, Function> CONVERTERS = Map.of( // // java.io and java.nio File.class, File::new, // @@ -38,7 +37,7 @@ class StringToCommonJavaTypesConverter implements StringToObjectConverter { URL.class, StringToCommonJavaTypesConverter::toURL, // java.util Currency.class, Currency::getInstance, // - Locale.class, Locale::new, // + Locale.class, Locale::forLanguageTag, // UUID.class, UUID::fromString // ); 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 0096a9fcd998..a96541568836 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 @@ -269,8 +269,9 @@ void usingTheRemovedScopeConfigurationParameterProducesWarning() { .configurationParameter("junit.jupiter.tempdir.scope", "per_context")); assertThat(results.getDiscoveryIssues()) // - .contains(DiscoveryIssue.create(Severity.WARNING, - "The 'junit.jupiter.tempdir.scope' configuration parameter is no longer supported: per_context. Please remove it from your configuration.")); + .contains(DiscoveryIssue.create(Severity.WARNING, """ + The 'junit.jupiter.tempdir.scope' configuration parameter is no longer supported. \ + Please remove it from your configuration.""")); } @Nested 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 194be12b1abb..f79252155109 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 @@ -11,6 +11,7 @@ package org.junit.jupiter.params; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -22,7 +23,6 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.appendTestTemplateInvocationSegment; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForTestTemplateMethod; -import static org.junit.jupiter.params.converter.DefaultArgumentConverter.DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectIteration; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; @@ -99,6 +99,7 @@ import org.junit.jupiter.params.converter.ArgumentConversionException; import org.junit.jupiter.params.converter.ArgumentConverter; import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.converter.TypedArgumentConverter; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -114,6 +115,8 @@ import org.junit.jupiter.params.support.ParameterDeclarations; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.ClassUtils; +import org.junit.platform.engine.DiscoveryIssue; +import org.junit.platform.engine.DiscoveryIssue.Severity; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -488,17 +491,20 @@ void executesWithDefaultLocaleConversionFormat() { } @Test - void executesWithBcp47LocaleConversionFormat() { - var results = execute(Map.of(DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME, "bcp_47"), - LocaleConversionTestCase.class, "testWithBcp47", Locale.class); + void emitsWarningForNoLongerSupportedConfigurationParameter() { + var results = discoverTests(request -> request // + .configurationParameter("junit.jupiter.params.arguments.conversion.locale.format", "iso_639") // + .selectors(selectMethod(LocaleConversionTestCase.class, "testWithBcp47", Locale.class))); - results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); + assertThat(results.getDiscoveryIssues()) // + .contains(DiscoveryIssue.create(Severity.WARNING, """ + The 'junit.jupiter.params.arguments.conversion.locale.format' configuration parameter \ + is no longer supported. Please remove it from your configuration.""")); } @Test - void executesWithIso639LocaleConversionFormat() { - var results = execute(Map.of(DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME, "iso_639"), - LocaleConversionTestCase.class, "testWithIso639", Locale.class); + void executesWithCustomLocalConverterUsingIso639Format() { + var results = execute(LocaleConversionTestCase.class, "testWithIso639", Locale.class); results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); } @@ -2566,11 +2572,24 @@ void testWithBcp47(Locale locale) { @ParameterizedTest @ValueSource(strings = "en-US") - void testWithIso639(Locale locale) { + void testWithIso639(@ConvertWith(Iso639Converter.class) Locale locale) { assertEquals("en-us", locale.getLanguage()); assertEquals("", locale.getCountry()); } + static class Iso639Converter extends TypedArgumentConverter { + + Iso639Converter() { + super(String.class, Locale.class); + } + + @SuppressWarnings("deprecation") + @Override + protected Locale convert(@Nullable String source) throws ArgumentConversionException { + return new Locale(requireNonNull(source)); + } + } + } private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { 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 8bad1f29d436..49ec02df7874 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 @@ -171,7 +171,7 @@ private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationI @Nullable Object... arguments) { var context = mock(ExtensionContext.class); var classLoader = DefaultArgumentsAccessorTests.class.getClassLoader(); - return DefaultArgumentsAccessor.create(context, invocationIndex, classLoader, arguments); + return DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments); } @SuppressWarnings("unused") 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 405828e97fbb..721d09d38952 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 @@ -12,26 +12,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.params.converter.DefaultArgumentConverter.DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME; -import static org.junit.jupiter.params.converter.DefaultArgumentConverter.LocaleConversionFormat.BCP_47; -import static org.junit.jupiter.params.converter.DefaultArgumentConverter.LocaleConversionFormat.ISO_639; import static org.junit.platform.commons.util.ClassLoaderUtils.getClassLoader; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -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; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.support.ReflectionSupport; @@ -46,8 +36,7 @@ */ class DefaultArgumentConverterTests { - private final ExtensionContext context = mock(); - private final DefaultArgumentConverter underTest = spy(new DefaultArgumentConverter(context)); + private final DefaultArgumentConverter underTest = spy(DefaultArgumentConverter.INSTANCE); @Test void isAwareOfNull() { @@ -112,36 +101,6 @@ void delegatesStringsConversion() { verify(underTest).convert("value", int.class, getClassLoader(DefaultArgumentConverterTests.class)); } - @Test - void convertsLocaleWithDefaultFormat() { - when(context.getConfigurationParameter(eq(DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME), any())) // - .thenReturn(Optional.empty()); - - assertConverts("en", Locale.class, Locale.ENGLISH); - assertConverts("en-US", Locale.class, Locale.US); - } - - @Test - void convertsLocaleWithExplicitBcp47Format() { - when(context.getConfigurationParameter(eq(DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME), any())) // - .thenReturn(Optional.of(BCP_47)); - - assertConverts("en", Locale.class, Locale.ENGLISH); - assertConverts("en-US", Locale.class, Locale.US); - } - - @Test - void delegatesLocaleConversionWithExplicitIso639Format() { - when(context.getConfigurationParameter(eq(DEFAULT_LOCALE_CONVERSION_FORMAT_PROPERTY_NAME), any())) // - .thenReturn(Optional.of(ISO_639)); - - doReturn(null).when(underTest).convert(any(), any(), any(ClassLoader.class)); - - convert("en", Locale.class); - - verify(underTest).convert("en", Locale.class, getClassLoader(DefaultArgumentConverterTests.class)); - } - @Test void throwsExceptionForDelegatedConversionFailure() { ConversionException exception = new ConversionException("fail"); diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt index cbd6ca3a787b..c9cc1cf7dbe5 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt @@ -56,7 +56,7 @@ class ArgumentsAccessorKotlinTests { ): DefaultArgumentsAccessor { val context = mock(ExtensionContext::class.java) val classLoader = ArgumentsAccessorKotlinTests::class.java.classLoader - return DefaultArgumentsAccessor.create(context, invocationIndex, classLoader, arguments) + return DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments) } fun foo() { 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 4b9cfaf1b4dd..4dc0fefb85e0 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 @@ -294,10 +294,9 @@ void convertsStringToCurrency() { } @Test - @SuppressWarnings("deprecation") void convertsStringToLocale() { assertConverts("en", Locale.class, Locale.ENGLISH); - assertConverts("en_us", Locale.class, new Locale(Locale.US.toString())); + assertConverts("en-US", Locale.class, Locale.US); } @Test From 0423dd0c05775d3627b90bd5440fac8388458d56 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:03:50 +0000 Subject: [PATCH 068/162] Update log4j2 monorepo to v2.25.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ead394f36f6..391b2094f103 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" ktlint = "1.6.0" -log4j = "2.25.0" +log4j = "2.25.1" logback = "1.5.18" opentest4j = "1.3.0" openTestReporting = "0.2.4" From e174bbad98d9925f0332d0e0d4703eb437aa66ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:03:46 +0000 Subject: [PATCH 069/162] Update dependency com.google.jimfs:jimfs to v1.3.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 391b2094f103..a1fc28f7b301 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } -jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.0" } +jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.1" } 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" } From 352110bc00b0c821289e162913bc99ce6f729622 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Jul 2025 09:28:47 +0200 Subject: [PATCH 070/162] Revert "Update dependency com.google.jimfs:jimfs to v1.3.1" (#4756) This reverts commit e174bbad98d9925f0332d0e0d4703eb437aa66ef. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1fc28f7b301..391b2094f103 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } -jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.1" } +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" } From 14dfedcac517a29c28c51ad052fe96f988a079ff Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 14:14:27 +0200 Subject: [PATCH 071/162] Avoid reference to `Assertions` class from production code `SeparateThreadTimeoutInvocation` called an internal overload of `Assertions.assertTimeoutPreemptively` to avoid having to duplicate the implementation of executing a block of code with a preemptive timeout. Instead, this commit moves the shared code to a new `PreemptiveTimeoutUtils` class in the new `org.junit.jupiter.api.util` package which is only exported to the `org.junit.jupiter.engine` module. --- .../junit-jupiter-api.gradle.kts | 3 + .../src/main/java/module-info.java | 1 + .../api/AssertTimeoutPreemptively.java | 98 +---------- .../org/junit/jupiter/api/Assertions.java | 49 ------ .../api/util/PreemptiveTimeoutUtils.java | 163 ++++++++++++++++++ .../junit/jupiter/api/util/package-info.java | 14 ++ .../SeparateThreadTimeoutInvocation.java | 4 +- ...ertTimeoutPreemptivelyAssertionsTests.java | 37 ---- .../api/util/PreemptiveTimeoutUtilsTest.java | 79 +++++++++ .../junit-jupiter-api.expected.txt | 1 + 10 files changed, 267 insertions(+), 182 deletions(-) create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtils.java create mode 100644 junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/package-info.java create mode 100644 jupiter-tests/src/test/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtilsTest.java diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index be4e103bada2..c0c075f7735f 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -25,6 +25,9 @@ dependencies { } tasks { + compileJava { + options.compilerArgs.add("-Xlint:-module") // due to qualified exports + } jar { bundle { val version = project.version diff --git a/junit-jupiter-api/src/main/java/module-info.java b/junit-jupiter-api/src/main/java/module-info.java index c41a935d2629..036ed66a2669 100644 --- a/junit-jupiter-api/src/main/java/module-info.java +++ b/junit-jupiter-api/src/main/java/module-info.java @@ -30,6 +30,7 @@ exports org.junit.jupiter.api.function; exports org.junit.jupiter.api.io; exports org.junit.jupiter.api.parallel; + exports org.junit.jupiter.api.util to org.junit.jupiter.engine; opens org.junit.jupiter.api.condition to org.junit.platform.commons; } 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 3f1009f56ec9..f6c6afd42a12 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,27 +10,15 @@ 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 static org.junit.jupiter.api.util.PreemptiveTimeoutUtils.executeWithPreemptiveTimeout; -import java.io.Serial; import java.time.Duration; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; -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; import org.opentest4j.AssertionFailedError; /** @@ -64,74 +52,21 @@ static void assertTimeoutPreemptively(Duration timeout, Executable executable, } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier) { - return assertTimeoutPreemptively(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); + return executeWithPreemptiveTimeout(timeout, supplier, null, AssertTimeoutPreemptively::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, @Nullable String message) { - return assertTimeoutPreemptively(timeout, supplier, message == null ? null : () -> message, + return executeWithPreemptiveTimeout(timeout, supplier, message == null ? null : () -> message, AssertTimeoutPreemptively::createAssertionFailure); } static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier) { - return assertTimeoutPreemptively(timeout, supplier, messageSupplier, + return executeWithPreemptiveTimeout(timeout, supplier, messageSupplier, AssertTimeoutPreemptively::createAssertionFailure); } - 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()); - - try { - Future future = submitTask(supplier, threadReference, executorService); - return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, - failureFactory); - } - finally { - executorService.shutdownNow(); - } - } - - private static Future submitTask(ThrowingSupplier supplier, - AtomicReference threadReference, ExecutorService executorService) { - return executorService.submit(() -> { - try { - threadReference.set(Thread.currentThread()); - return supplier.get(); - } - catch (Throwable throwable) { - throw throwAsUncheckedException(throwable); - } - }); - } - - 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); - } - catch (TimeoutException ex) { - Thread thread = threadSupplier.get(); - ExecutionTimeoutException cause = null; - if (thread != null) { - cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); - cause.setStackTrace(thread.getStackTrace()); - } - throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause, thread); - } - catch (ExecutionException ex) { - throw throwAsUncheckedException(requireNonNullElse(ex.getCause(), ex)); - } - catch (Throwable ex) { - throw throwAsUncheckedException(ex); - } - } - private static AssertionFailedError createAssertionFailure(Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, @Nullable Throwable cause, @Nullable Thread thread) { return assertionFailure() // @@ -141,31 +76,6 @@ private static AssertionFailedError createAssertionFailure(Duration timeout, .build(); } - private static class ExecutionTimeoutException extends JUnitException { - - @Serial - private static final long serialVersionUID = 1L; - - ExecutionTimeoutException(String message) { - super(message); - } - } - - /** - * The thread factory used for preemptive timeout. - * - *

    The factory creates threads with meaningful names, helpful for debugging - * purposes. - */ - private static class TimeoutThreadFactory implements ThreadFactory { - private static final AtomicInteger threadNumber = new AtomicInteger(1); - - @Override - public Thread newThread(Runnable r) { - return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); - } - } - private AssertTimeoutPreemptively() { } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java index cc928e0eade9..b551f44de874 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.api; -import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import java.time.Duration; @@ -3654,36 +3653,6 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier); } - /** - * Assert that execution of the supplied {@code supplier} - * completes before the given {@code timeout} is exceeded. - * - *

    See the {@linkplain Assertions Preemptive Timeouts} section of the - * class-level Javadoc for further details. - * - *

    If the assertion passes then the {@code supplier}'s result is returned. - * - *

    In the case the assertion does not pass, the supplied - * {@link TimeoutFailureFactory} is invoked to create an exception which is - * then thrown. - * - *

    If necessary, the failure message will be retrieved lazily from the - * supplied {@code messageSupplier}. - * - * @see #assertTimeoutPreemptively(Duration, Executable) - * @see #assertTimeoutPreemptively(Duration, Executable, String) - * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier) - * @see #assertTimeoutPreemptively(Duration, ThrowingSupplier, String) - * @see #assertTimeout(Duration, Executable, Supplier) - */ - @API(status = INTERNAL, since = "5.9.1") - public static T assertTimeoutPreemptively(Duration timeout, - ThrowingSupplier supplier, Supplier<@Nullable String> messageSupplier, - TimeoutFailureFactory failureFactory) throws E { - return AssertTimeoutPreemptively.assertTimeoutPreemptively(timeout, supplier, messageSupplier, failureFactory); - } - // --- assertInstanceOf ---------------------------------------------------- /** @@ -3735,22 +3704,4 @@ public static T assertInstanceOf(Class expectedType, @Nullable Object act return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, messageSupplier); } - /** - * Factory for timeout failures. - * - * @param The type of error or exception created - * @since 5.9.1 - * @see Assertions#assertTimeoutPreemptively(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) - */ - @API(status = INTERNAL, since = "5.9.1") - public interface TimeoutFailureFactory { - - /** - * Create a failure for the given timeout, message, and cause. - * - * @return timeout failure; never {@code null} - */ - 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/util/PreemptiveTimeoutUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtils.java new file mode 100644 index 000000000000..bf81a94ea4a4 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtils.java @@ -0,0 +1,163 @@ +/* + * 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.jupiter.api.util; + +import static java.util.Objects.requireNonNullElse; +import static org.apiguardian.api.API.Status.INTERNAL; +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; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.JUnitException; + +/** + * Internal utilities for executing code with a preemptive timeout. + * + * @since 6.0 + */ +@API(status = INTERNAL, since = "6.0") +public class PreemptiveTimeoutUtils { + + /** + * Assert that execution of the supplied {@code supplier} + * completes before the given {@code timeout} is exceeded. + * + *

    See the {@linkplain Assertions Preemptive Timeouts} section of the + * class-level Javadoc for further details. + * + *

    If the assertion passes then the {@code supplier}'s result is returned. + * + *

    In the case the assertion does not pass, the supplied + * {@link TimeoutFailureFactory} is invoked to create an exception which is + * then thrown. + * + *

    If necessary, the failure message will be retrieved lazily from the + * supplied {@code messageSupplier}. + */ + public static T executeWithPreemptiveTimeout(Duration timeout, + ThrowingSupplier supplier, @Nullable Supplier<@Nullable String> messageSupplier, + TimeoutFailureFactory failureFactory) throws E { + + AtomicReference threadReference = new AtomicReference<>(); + ExecutorService executorService = Executors.newSingleThreadExecutor(new TimeoutThreadFactory()); + + try { + Future future = submitTask(supplier, threadReference, executorService); + return resolveFutureAndHandleException(future, timeout, messageSupplier, threadReference::get, + failureFactory); + } + finally { + executorService.shutdownNow(); + } + } + + private static Future submitTask(ThrowingSupplier supplier, + AtomicReference threadReference, ExecutorService executorService) { + return executorService.submit(() -> { + try { + threadReference.set(Thread.currentThread()); + return supplier.get(); + } + catch (Throwable throwable) { + throw throwAsUncheckedException(throwable); + } + }); + } + + private static T resolveFutureAndHandleException(Future future, + Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, + Supplier<@Nullable Thread> threadSupplier, TimeoutFailureFactory failureFactory) + throws E, RuntimeException { + try { + return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS); + } + catch (TimeoutException ex) { + Thread thread = threadSupplier.get(); + ExecutionTimeoutException cause = null; + if (thread != null) { + cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); + cause.setStackTrace(thread.getStackTrace()); + } + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause, thread); + } + catch (ExecutionException ex) { + throw throwAsUncheckedException(requireNonNullElse(ex.getCause(), ex)); + } + catch (Throwable ex) { + throw throwAsUncheckedException(ex); + } + } + + private PreemptiveTimeoutUtils() { + } + + /** + * Factory for timeout failures. + * + * @param The type of error or exception created + * @since 5.9.1 + * @see PreemptiveTimeoutUtils#executeWithPreemptiveTimeout(Duration, ThrowingSupplier, Supplier, TimeoutFailureFactory) + */ + public interface TimeoutFailureFactory { + + /** + * Create a failure for the given timeout, message, and cause. + * + * @return timeout failure; never {@code null} + */ + T createTimeoutFailure(Duration timeout, @Nullable Supplier<@Nullable String> messageSupplier, + @Nullable Throwable cause, @Nullable Thread testThread); + } + + private static class ExecutionTimeoutException extends JUnitException { + + @Serial + private static final long serialVersionUID = 1L; + + ExecutionTimeoutException(String message) { + super(message); + } + + } + + /** + * The thread factory used for preemptive timeout. + * + *

    The factory creates threads with meaningful names, helpful for debugging + * purposes. + */ + private static class TimeoutThreadFactory implements ThreadFactory { + + private static final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "junit-timeout-thread-" + threadNumber.getAndIncrement()); + } + + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/package-info.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/package-info.java new file mode 100644 index 000000000000..29d12b7ba0ce --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/util/package-info.java @@ -0,0 +1,14 @@ +/** + * Internal JUnit Jupiter utilities. + * + *

    DISCLAIMER

    + * + *

    These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + */ + +@NullMarked +package org.junit.jupiter.api.util; + +import org.jspecify.annotations.NullMarked; 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 03635bddcc08..efe760e66c69 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 @@ -10,7 +10,7 @@ package org.junit.jupiter.engine.extension; -import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.util.PreemptiveTimeoutUtils.executeWithPreemptiveTimeout; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; @@ -39,7 +39,7 @@ class SeparateThreadTimeoutInvocation implements Inv @Override @SuppressWarnings("NullAway") public T proceed() throws Throwable { - return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, + return executeWithPreemptiveTimeout(timeout.toDuration(), delegate::proceed, descriptionSupplier, (__, ___, cause, testThread) -> { TimeoutException exception = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, null); if (testThread != null) { 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 b7fb03847676..5a7dc972c8a8 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 @@ -23,7 +23,6 @@ import java.time.Duration; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -40,8 +39,6 @@ class AssertTimeoutPreemptivelyAssertionsTests { private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); - private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, ____, - _____) -> new TimeoutException(); private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); @@ -198,40 +195,6 @@ void assertTimeoutPreemptivelyUsesThreadsWithSpecificNamePrefix() { .startsWith("junit-timeout-thread-"); } - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { - assertThrows(TimeoutException.class, () -> Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> { - waitForInterrupt(); - return "Tempus Fugit"; - }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { - AssertionFailedError exception = assertThrows(AssertionFailedError.class, - () -> Assertions.assertTimeoutPreemptively(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", - TIMEOUT_EXCEPTION_FACTORY)); - assertMessageEquals(exception, "enigma"); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { - RuntimeException exception = assertThrows(RuntimeException.class, - () -> Assertions.assertTimeoutPreemptively(ofMillis(500), - () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", - TIMEOUT_EXCEPTION_FACTORY)); - assertMessageEquals(exception, ":("); - } - - @Test - void assertTimeoutPreemptivelyThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() - throws Exception { - var result = Assertions.assertTimeoutPreemptively(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", - () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY); - - assertThat(result).isEqualTo("Tempus Fugit"); - } - private void waitForInterrupt() { try { assertFalse(Thread.interrupted(), "Already interrupted"); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtilsTest.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtilsTest.java new file mode 100644 index 000000000000..6aacd80ca13b --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/util/PreemptiveTimeoutUtilsTest.java @@ -0,0 +1,79 @@ +/* + * 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.jupiter.api.util; + +import static java.time.Duration.ofMillis; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.OS.WINDOWS; +import static org.junit.jupiter.api.util.PreemptiveTimeoutUtils.executeWithPreemptiveTimeout; + +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ExceptionUtils; +import org.opentest4j.AssertionFailedError; + +class PreemptiveTimeoutUtilsTest { + + private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); + private static final PreemptiveTimeoutUtils.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, + ___, ____, _____) -> new TimeoutException(); + + @Test + void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatCompletesAfterTheTimeout() { + assertThrows(TimeoutException.class, () -> executeWithPreemptiveTimeout(PREEMPTIVE_TIMEOUT, () -> { + waitForInterrupt(); + return "Tempus Fugit"; + }, () -> "Tempus Fugit", TIMEOUT_EXCEPTION_FACTORY)); + } + + @Test + void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnAssertionFailedError() { + AssertionFailedError exception = assertThrows(AssertionFailedError.class, + () -> executeWithPreemptiveTimeout(ofMillis(500), () -> fail("enigma"), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); + assertThat(exception).hasMessage("enigma"); + } + + @Test + void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatThrowsAnException() { + RuntimeException exception = assertThrows(RuntimeException.class, + () -> executeWithPreemptiveTimeout(ofMillis(500), + () -> ExceptionUtils.throwAsUncheckedException(new RuntimeException(":(")), () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY)); + assertThat(exception).hasMessage(":("); + } + + @Test + void executeWithPreemptiveTimeoutThrowingTimeoutExceptionWithMessageForSupplierThatCompletesBeforeTimeout() + throws Exception { + var result = executeWithPreemptiveTimeout(PREEMPTIVE_TIMEOUT, () -> "Tempus Fugit", () -> "Tempus Fugit", + TIMEOUT_EXCEPTION_FACTORY); + + assertThat(result).isEqualTo("Tempus Fugit"); + } + + private void waitForInterrupt() { + try { + assertFalse(Thread.interrupted(), "Already interrupted"); + new CountDownLatch(1).await(); + } + catch (InterruptedException ignore) { + // ignore + } + } + +} 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 e0dc613a1e0f..8c3bc5d34ccc 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 @@ -12,4 +12,5 @@ requires org.apiguardian.api static transitive requires org.jspecify static transitive requires org.junit.platform.commons transitive requires org.opentest4j transitive +qualified exports org.junit.jupiter.api.util to org.junit.jupiter.engine qualified opens org.junit.jupiter.api.condition to org.junit.platform.commons From 64345f1a501aeaafbf7f6c055de9bb9e85464f48 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 14:15:00 +0200 Subject: [PATCH 072/162] Add ArchUnit rule ensuring Jupiter Assertions are self-contained --- .../platform/tooling/support/tests/ArchUnitTests.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 bfb4882b58d6..a6922a5d253b 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 @@ -22,6 +22,7 @@ import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; +import static com.tngtech.archunit.lang.conditions.ArchConditions.onlyBeAccessedByClassesThat; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -50,6 +51,8 @@ import org.apiguardian.api.API; import org.jspecify.annotations.NullMarked; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; @AnalyzeClasses(packages = { "org.junit.platform", "org.junit.jupiter", "org.junit.vintage" }) class ArchUnitTests { @@ -80,6 +83,14 @@ class ArchUnitTests { .should(haveContainerAnnotationWithSameRetentionPolicy()) // .andShould(haveContainerAnnotationWithSameTargetTypes()); + private final DescribedPredicate jupiterAssertions = name(Assertions.class.getName()) // + .or(name(Assumptions.class.getName())).or(name("org.junit.jupiter.api.AssertionsKt")); + + @SuppressWarnings("unused") + @ArchTest // https://github.com/junit-team/junit-framework/issues/4604 + private final ArchRule jupiterAssertionsShouldBeSelfContained = classes().that(jupiterAssertions) // + .should(onlyBeAccessedByClassesThat(jupiterAssertions)); + @ArchTest void packagesShouldBeNullMarked(JavaClasses classes) { var exclusions = Stream.of( // From 6437024401016f49f1c46e4c73e2d6f2a5759d38 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 14:15:23 +0200 Subject: [PATCH 073/162] Add Checkstyle rule ensuring Jupiter Assertions are self-contained --- gradle/config/checkstyle/checkstyleMain.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index eecf914bb4d4..53d749b69fba 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -34,6 +34,14 @@ + + + + + + + + From 7c41b13256f073bb64f54a95aaad8c252608d44d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 10 Jul 2025 14:29:41 +0200 Subject: [PATCH 074/162] Document how to use Checkstyle rules to forbid Jupiter assertions --- .../src/docs/asciidoc/link-attributes.adoc | 1 + .../asciidoc/user-guide/writing-tests.adoc | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index d9ee5abf73e5..1504f7fc063d 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -240,6 +240,7 @@ endif::[] :API: https://apiguardian-team.github.io/apiguardian/docs/current/api/[@API] :API_Guardian: https://github.com/apiguardian-team/apiguardian[@API Guardian] :AssertJ: https://assertj.github.io/doc/[AssertJ] +:Checkstyle: https://checkstyle.sourceforge.io[Checkstyle] :DiscussionsQA: https://github.com/junit-team/junit-framework/discussions/categories/q-a[Q&A category on GitHub Discussions] :Hamcrest: https://hamcrest.org/JavaHamcrest/[Hamcrest] :Jimfs: https://google.github.io/jimfs/[Jimfs] diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 7340e0ac5b3e..6b26c7b2b86d 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -519,6 +519,32 @@ include::{testDir}/example/HamcrestAssertionsDemo.java[tags=user_guide] Naturally, legacy tests based on the JUnit 4 programming model can continue using `org.junit.Assert#assertThat`. +[TIP] +.Excluding Jupiter’s Assertions From a Project’s Classpath +==== +If you would like to enforce that all your tests use a certain third-party assertion +library instead of Jupiter's, you can set up a rule using {Checkstyle} or another static +analysis tool that fails the build if Jupiter's `Assertions` class is used. + +[source,xml] +---- + + + + + + + + + + + + + + +---- +==== + [[writing-tests-assumptions]] === Assumptions From 1d3a5eafabe68e9c3ade9c303aeb9e18fecb52f2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 12 Jul 2025 07:29:48 +0000 Subject: [PATCH 075/162] Update dependency com.google.jimfs:jimfs to v1.3.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 391b2094f103..a1fc28f7b301 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } -jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.0" } +jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.1" } 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" } From 8f2a49b226129a0b181f53d5bf3646482e35487f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Jul 2025 14:13:30 +0200 Subject: [PATCH 076/162] Add Guava to module graph Guava now includes a module descriptor but Jimfs doesn't so we need to manually add it to the module graph as a root module. --- .../platform/tooling/support/tests/ModularUserGuideTests.java | 3 +++ 1 file changed, 3 insertions(+) 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 7882fc19847b..9081c9066a6c 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 @@ -94,6 +94,7 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); ThirdPartyJars.copy(lib, "com.google.jimfs", "jimfs"); ThirdPartyJars.copy(lib, "com.google.guava", "guava"); + ThirdPartyJars.copy(lib, "com.google.guava", "failureaccess"); loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -136,6 +137,7 @@ private static void junit(Path temp, OutputFiles outputFiles) throws Exception { )) // .addArguments("--add-modules", "documentation") // .addArguments("--patch-module", "documentation=" + projectDir.resolve("src/test/resources")) // + .addArguments("--add-modules", "com.google.common") // .addArguments("--module", "org.junit.platform.console") // .addArguments("execute") // .addArguments("--scan-modules") // @@ -170,6 +172,7 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, "lib/apiguardian-api-.+\\.jar", // "lib/assertj-core-.+\\.jar", // "lib/byte-buddy-.+", // + "lib/failureaccess-.+\\.jar", // "lib/guava-.+\\.jar", // "lib/hamcrest-.+\\.jar", // "lib/jimfs-.+\\.jar", // From d2d9987282c39e047a6d1137613eaf2750b1aad7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Jul 2025 14:31:20 +0200 Subject: [PATCH 077/162] Copy all jars resolved by Gradle for `ModularUserGuideTests` --- .../platform-tooling-support-tests.gradle.kts | 4 ++- .../tooling/support/ThirdPartyJars.java | 30 +++++++++++++++---- .../support/tests/ModularUserGuideTests.java | 29 ++---------------- 3 files changed, 30 insertions(+), 33 deletions(-) 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 6253915759c8..0663e10ceaeb 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -58,7 +58,9 @@ dependencies { because("it uses the OS enum to support Windows") } - thirdPartyJars(libs.junit4) + thirdPartyJars(libs.junit4) { + exclude(group = "org.hamcrest") + } thirdPartyJars(libs.assertj) thirdPartyJars(libs.apiguardian) thirdPartyJars(libs.hamcrest) diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java index 7873f4cae08d..644e9cfccfc3 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java @@ -13,6 +13,8 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; @@ -22,16 +24,34 @@ public class ThirdPartyJars { private ThirdPartyJars() { } - public static void copy(Path targetDir, String group, String artifact) throws Exception { + public static void copy(Path targetDir, String group, String artifact) { Path source = find(group, artifact); - Files.copy(source, targetDir.resolve(source.getFileName()), REPLACE_EXISTING); + copy(source, targetDir); + } + + public static void copyAll(Path targetDir) { + thirdPartyJars().forEach(path -> copy(path, targetDir)); + } + + private static void copy(Path source, Path targetDir) { + try { + Files.copy(source, targetDir.resolve(source.getFileName()), REPLACE_EXISTING); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to copy %s to %s".formatted(source, targetDir), e); + } } public static Path find(String group, String artifact) { - return Stream.of(System.getProperty("thirdPartyJars").split(File.pathSeparator)) // - .filter(it -> it.replace(File.separator, "/").contains("/" + group + "/" + artifact + "/")) // - .map(Path::of) // + return thirdPartyJars() // + .filter(it -> it.toAbsolutePath().toString().replace(File.separator, "/").contains( + "/" + group + "/" + artifact + "/")) // .findFirst() // .orElseThrow(() -> new AssertionError("Failed to find JAR file for " + group + ":" + artifact)); } + + private static Stream thirdPartyJars() { + return Stream.of(System.getProperty("thirdPartyJars").split(File.pathSeparator)) // + .map(Path::of); + } } 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 9081c9066a6c..a92420a5c6ae 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 @@ -83,18 +83,7 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex args.add(temp.resolve("destination").toString()); var lib = Files.createDirectories(temp.resolve("lib")); - ThirdPartyJars.copy(lib, "junit", "junit"); - ThirdPartyJars.copy(lib, "org.assertj", "assertj-core"); - // Byte Buddy is used by AssertJ's soft assertions which are used by the Engine Test Kit - 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"); - ThirdPartyJars.copy(lib, "com.google.guava", "guava"); - ThirdPartyJars.copy(lib, "com.google.guava", "failureaccess"); + ThirdPartyJars.copyAll(lib); loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -161,31 +150,17 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, var err = new StringWriter(); var args = compile(temp, out, err); - // args.forEach(System.out::println); assertTrue(err.toString().isBlank(), () -> err + "\n\n" + String.join("\n", args)); var listing = treeWalk(temp); assertLinesMatch(List.of( // "destination", // - ">> CLASSES >>", // - "lib", // - "lib/apiguardian-api-.+\\.jar", // - "lib/assertj-core-.+\\.jar", // - "lib/byte-buddy-.+", // - "lib/failureaccess-.+\\.jar", // - "lib/guava-.+\\.jar", // - "lib/hamcrest-.+\\.jar", // - "lib/jimfs-.+\\.jar", // - "lib/jspecify-.+\\.jar", // - "lib/junit-.+\\.jar", // - ">> ALL JUNIT 5 JARS >>", // + ">> CLASSES AND JARS >>", // "lib/opentest4j-.+\\.jar", // "src", // "src/documentation", // "src/documentation/module-info.java" // ), listing); - // System.out.println("______________"); - // listing.forEach(System.out::println); junit(temp, outputFiles); } From 623652ac2f717d5f55760484509e43ce3b41229e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 13 Jul 2025 14:45:32 +0200 Subject: [PATCH 078/162] Remove and forbid redundant modifiers --- .../src/test/java/example/ParameterizedTestDemo.java | 3 +++ .../extensions/ParameterResolverCustomTypeDemo.java | 2 +- .../example/testinterface/TestLifecycleLogger.java | 2 +- gradle/config/checkstyle/checkstyleMain.xml | 1 + gradle/config/checkstyle/checkstyleTest.xml | 1 + .../InstantiatingConfigurationParameterConverter.java | 2 +- .../engine/descriptor/ClassTemplateTestDescriptor.java | 2 +- .../engine/execution/ConditionEvaluationException.java | 2 +- .../engine/extension/MutableExtensionRegistry.java | 2 +- .../extension/RepeatedTestInvocationContext.java | 3 +-- .../junit/jupiter/engine/extension/TempDirectory.java | 2 +- .../org/junit/platform/console/tasks/ColorPalette.java | 8 ++++---- .../platform/console/tasks/StandardStreamsHandler.java | 2 +- .../main/java/org/junit/platform/engine/UniqueId.java | 10 +++++----- .../engine/support/descriptor/ClassSource.java | 6 +++--- .../support/descriptor/ClasspathResourceSource.java | 2 +- .../engine/support/descriptor/CompositeTestSource.java | 2 +- .../engine/support/descriptor/DirectorySource.java | 4 ++-- .../platform/engine/support/descriptor/FileSource.java | 6 +++--- .../engine/support/descriptor/MethodSource.java | 8 ++++---- .../engine/support/descriptor/PackageSource.java | 2 +- .../engine/support/hierarchical/LockManager.java | 2 +- .../platform/reporting/open/xml/GitInfoCollector.java | 2 +- .../engine/VintageTestEngineExecutionTests.java | 3 +++ .../extension/ExecutableInvokerIntegrationTests.java | 9 ++++++--- .../java/org/junit/jupiter/engine/ReportingTests.java | 2 +- .../jupiter/engine/TestInstanceLifecycleTests.java | 2 +- .../engine/extension/InvocationInterceptorTests.java | 2 +- .../LifecycleMethodExecutionExceptionHandlerTests.java | 2 +- .../OrderedProgrammaticExtensionRegistrationTests.java | 6 +++--- .../ProgrammaticExtensionRegistrationTests.java | 2 +- .../jupiter/engine/extension/RepeatedTestTests.java | 2 +- .../extension/TestInfoParameterResolverTests.java | 2 +- .../engine/extension/TestInstanceFactoryTests.java | 4 ++-- ...penTest4JAndJUnit4AwareThrowableCollectorTests.java | 2 +- .../params/ParameterizedClassIntegrationTests.java | 2 +- .../params/ParameterizedTestIntegrationTests.java | 4 ++-- .../platform/commons/support/ModifierSupportTests.java | 1 + .../platform/commons/util/ReflectionUtilsTests.java | 2 +- .../SuiteLauncherDiscoveryRequestBuilderTests.java | 4 ++-- .../platform/tooling/support/tests/ArchUnitTests.java | 2 +- .../support/tests/JarContainsManifestFirstTests.java | 2 +- 42 files changed, 71 insertions(+), 60 deletions(-) diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index 72caa35d7ba4..dc41e179994d 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -381,6 +381,9 @@ public class MyArgumentsProviderWithConstructorInjection implements ArgumentsPro private final TestInfo testInfo; + // end::ArgumentsProviderWithConstructorInjection_example[] + @SuppressWarnings("RedundantModifier") + // tag::ArgumentsProviderWithConstructorInjection_example[] public MyArgumentsProviderWithConstructorInjection(TestInfo testInfo) { this.testInfo = testInfo; } diff --git a/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java index 7cf2963985e2..b651076d4ce1 100644 --- a/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java +++ b/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java @@ -58,7 +58,7 @@ static class WrappedInteger { private final int value; - public WrappedInteger(int value) { + WrappedInteger(int value) { this.value = value; } diff --git a/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java index 0906b8674e6c..9bc7932642f2 100644 --- a/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java +++ b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java @@ -25,7 +25,7 @@ @TestInstance(Lifecycle.PER_CLASS) interface TestLifecycleLogger { - static final Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName()); + Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName()); @BeforeAll default void beforeAllTests() { diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index 53d749b69fba..51a5906293f9 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -44,6 +44,7 @@ + diff --git a/gradle/config/checkstyle/checkstyleTest.xml b/gradle/config/checkstyle/checkstyleTest.xml index 2e81cea24a3b..f564bd1c4872 100644 --- a/gradle/config/checkstyle/checkstyleTest.xml +++ b/gradle/config/checkstyle/checkstyleTest.xml @@ -26,6 +26,7 @@ + diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java index cb00c012d527..5e5d3f4e8e82 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java @@ -29,7 +29,7 @@ class InstantiatingConfigurationParameterConverter { private final Class clazz; private final String name; - public InstantiatingConfigurationParameterConverter(Class clazz, String name) { + InstantiatingConfigurationParameterConverter(Class clazz, String name) { this.clazz = clazz; this.name = name; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java index e796a620b8a5..dfabcb222079 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java @@ -209,7 +209,7 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte class ClassTemplateExecutor extends TemplateExecutor { - public ClassTemplateExecutor() { + ClassTemplateExecutor() { super(ClassTemplateTestDescriptor.this, ClassTemplateInvocationContextProvider.class); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java index 35f7b29f79dd..993564a8f7c4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java @@ -27,7 +27,7 @@ class ConditionEvaluationException extends JUnitException { @Serial private static final long serialVersionUID = 1L; - public ConditionEvaluationException(String message, Throwable cause) { + ConditionEvaluationException(String message, Throwable cause) { super(message, cause); } 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 fdd84569d7e1..f151a02fd5c2 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 @@ -294,7 +294,7 @@ private static class LateInitEntry implements Entry { @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private Optional extension = Optional.empty(); - public LateInitEntry(Class testClass, Function initializer) { + LateInitEntry(Class testClass, Function initializer) { this.testClass = testClass; this.initializer = initializer; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java index d8a735849d7e..dfacf5767f08 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java @@ -27,8 +27,7 @@ class RepeatedTestInvocationContext implements TestTemplateInvocationContext { private final DefaultRepetitionInfo repetitionInfo; private final RepeatedTestDisplayNameFormatter formatter; - public RepeatedTestInvocationContext(DefaultRepetitionInfo repetitionInfo, - RepeatedTestDisplayNameFormatter formatter) { + RepeatedTestInvocationContext(DefaultRepetitionInfo repetitionInfo, RepeatedTestDisplayNameFormatter formatter) { this.repetitionInfo = repetitionInfo; this.formatter = formatter; 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 954099cbf1da..b694f3137ca4 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 @@ -96,7 +96,7 @@ class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterR private final JupiterConfiguration configuration; - public TempDirectory(JupiterConfiguration configuration) { + TempDirectory(JupiterConfiguration configuration) { this.configuration = configuration; } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java index 34c1aadd287c..973366615f68 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java @@ -64,7 +64,7 @@ private static Map singleColorPalette() { return colorsToAnsiSequences; } - public ColorPalette(Map overrides) { + ColorPalette(Map overrides) { this(defaultPalette(), false); if (overrides.containsKey(Style.NONE)) { @@ -73,15 +73,15 @@ public ColorPalette(Map overrides) { this.colorsToAnsiSequences.putAll(overrides); } - public ColorPalette(Properties properties) { + ColorPalette(Properties properties) { this(toOverrideMap(properties)); } - public ColorPalette(Reader reader) { + ColorPalette(Reader reader) { this(getProperties(reader)); } - public ColorPalette(Path path) { + ColorPalette(Path path) { this(getProperties(path)); } 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 5f7b8bca1307..fe4a0b3f7033 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 @@ -24,7 +24,7 @@ class StandardStreamsHandler implements AutoCloseable { private @Nullable PrintStream stderr; - public StandardStreamsHandler() { + StandardStreamsHandler() { } /** 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 d98070bafb78..5f2549b44232 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 @@ -109,7 +109,7 @@ private UniqueId(UniqueIdFormat uniqueIdFormat, Segment segment) { this.segments = List.copyOf(segments); } - final Optional getRoot() { + Optional getRoot() { return this.segments.isEmpty() ? Optional.empty() : Optional.of(this.segments.get(0)); } @@ -118,7 +118,7 @@ final Optional getRoot() { * * @see #forEngine(String) */ - public final Optional getEngineId() { + public Optional getEngineId() { return getRoot().filter(segment -> ENGINE_SEGMENT_TYPE.equals(segment.getType())).map(Segment::getValue); } @@ -126,7 +126,7 @@ public final Optional getEngineId() { * Get the immutable list of {@linkplain Segment segments} that make up this * {@code UniqueId}. */ - public final List getSegments() { + public List getSegments() { return this.segments; } @@ -144,7 +144,7 @@ public final List getSegments() { * @param segmentType the type of the segment; never {@code null} or blank * @param value the value of the segment; never {@code null} or blank */ - public final UniqueId append(String segmentType, String value) { + public UniqueId append(String segmentType, String value) { return append(new Segment(segmentType, value)); } @@ -159,7 +159,7 @@ public final UniqueId append(String segmentType, String value) { * @since 1.1 */ @API(status = STABLE, since = "1.1") - public final UniqueId append(Segment segment) { + public UniqueId append(Segment segment) { Preconditions.notNull(segment, "segment must not be null"); List baseSegments = new ArrayList<>(this.segments.size() + 1); baseSegments.addAll(this.segments); 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 07304feec0e1..692355c37cc9 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 @@ -163,7 +163,7 @@ private ClassSource(Class javaClass, @Nullable FilePosition filePosition) { * @see #getJavaClass() * @see #getPosition() */ - public final String getClassName() { + public String getClassName() { return this.className; } @@ -177,7 +177,7 @@ public final String getClassName() { * @see #getClassName() * @see #getPosition() */ - public final Class getJavaClass() { + public Class getJavaClass() { if (this.javaClass == null) { // @formatter:off this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getNonNullOrThrow( @@ -194,7 +194,7 @@ public final Class getJavaClass() { * @see #getClassName() * @see #getJavaClass() */ - public final Optional getPosition() { + public Optional getPosition() { return Optional.ofNullable(this.filePosition); } 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 988f8c314e2b..6f4a476bc4b6 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 @@ -144,7 +144,7 @@ public String getClasspathResourceName() { /** * Get the {@link FilePosition}, if available. */ - public final Optional getPosition() { + public Optional getPosition() { return Optional.ofNullable(this.filePosition); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java index 59b94cc5ca90..1e0e187d8791 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java @@ -64,7 +64,7 @@ private CompositeTestSource(Collection sources) { * * @return the sources stored in this {@code CompositeTestSource}; never {@code null} */ - public final List getSources() { + public List getSources() { return this.sources; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java index 79505ee177fd..ca9980395a65 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java @@ -62,7 +62,7 @@ private DirectorySource(File directory) { * @return the source {@code URI}; never {@code null} */ @Override - public final URI getUri() { + public URI getUri() { return getFile().toURI(); } @@ -72,7 +72,7 @@ public final URI getUri() { * @return the source directory; never {@code null} */ @Override - public final File getFile() { + public File getFile() { return this.directory; } 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 dd253f6a0087..6e7a3ac95452 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 @@ -83,7 +83,7 @@ private FileSource(File file, @Nullable FilePosition filePosition) { * @return the source {@code URI}; never {@code null} */ @Override - public final URI getUri() { + public URI getUri() { return getFile().toURI(); } @@ -93,14 +93,14 @@ public final URI getUri() { * @return the source file; never {@code null} */ @Override - public final File getFile() { + public File getFile() { return this.file; } /** * Get the {@link FilePosition}, if available. */ - public final Optional getPosition() { + public Optional getPosition() { return Optional.ofNullable(this.filePosition); } 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 cde3533a72b0..62e3e16680dc 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 @@ -155,14 +155,14 @@ public String getClassName() { /** * Get the method name of this source. */ - public final String getMethodName() { + public String getMethodName() { return this.methodName; } /** * Get the method parameter types of this source. */ - public final @Nullable String getMethodParameterTypes() { + public @Nullable String getMethodParameterTypes() { return this.methodParameterTypes; } @@ -177,7 +177,7 @@ public final String getMethodName() { * @see #getClassName() */ @API(status = STABLE, since = "1.7") - public final Class getJavaClass() { + public Class getJavaClass() { return lazyLoadJavaClass(); } @@ -192,7 +192,7 @@ public final Class getJavaClass() { * @see #getMethodName() */ @API(status = STABLE, since = "1.7") - public final Method getJavaMethod() { + public Method getJavaMethod() { return lazyLoadJavaMethod(); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java index 94befac98143..702c440eda83 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java @@ -66,7 +66,7 @@ private PackageSource(String packageName) { /** * Get the package name of this test source. */ - public final String getPackageName() { + public String getPackageName() { return this.packageName; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java index 3171c31d2b66..2e63e4ecc917 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java @@ -37,7 +37,7 @@ class LockManager { private final SingleLock globalReadLock; private final SingleLock globalReadWriteLock; - public LockManager() { + LockManager() { globalReadLock = new SingleLock(GLOBAL_READ, toLock(GLOBAL_READ)); globalReadWriteLock = new SingleLock(GLOBAL_READ_WRITE, toLock(GLOBAL_READ_WRITE)); } diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/GitInfoCollector.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/GitInfoCollector.java index 1bd95732f2d4..e977a55e772d 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/GitInfoCollector.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/GitInfoCollector.java @@ -54,7 +54,7 @@ class CliGitInfoCollector implements GitInfoCollector { private final ProcessExecutor executor; - public CliGitInfoCollector(ProcessExecutor executor) { + CliGitInfoCollector(ProcessExecutor executor) { this.executor = executor; } diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index ef886c6c5699..9052d1afae25 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -590,6 +590,7 @@ public static class DynamicSuiteRunner extends Runner { private final Class testClass; + @SuppressWarnings("RedundantModifier") public DynamicSuiteRunner(Class testClass) { this.testClass = testClass; } @@ -630,6 +631,7 @@ public static class DynamicAndStaticChildrenRunner extends Runner { private final Class testClass; + @SuppressWarnings("RedundantModifier") public DynamicAndStaticChildrenRunner(Class testClass) { this.testClass = testClass; } @@ -685,6 +687,7 @@ public static class MisbehavingChildlessRunner extends Runner { private final Class testClass; + @SuppressWarnings("RedundantModifier") public MisbehavingChildlessRunner(Class testClass) { this.testClass = testClass; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java index 34e3f284596a..0443a57c8fa5 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java @@ -13,6 +13,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.lang.reflect.Constructor; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; @@ -62,7 +64,7 @@ static class ExecuteConstructorTwiceTestCase { static int constructorInvocations = 0; - public ExecuteConstructorTwiceTestCase(TestInfo testInfo, + ExecuteConstructorTwiceTestCase(TestInfo testInfo, @ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) { assertNotNull(testInfo); assertEquals(testInfo.getTestClass().orElseThrow(), extensionContext.getRequiredTestClass()); @@ -89,8 +91,9 @@ static class ExecuteConstructorTwiceExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { - context.getExecutableInvoker() // - .invoke(context.getRequiredTestClass().getConstructor(TestInfo.class, ExtensionContext.class)); + Constructor constructor = context.getRequiredTestClass() // + .getDeclaredConstructor(TestInfo.class, ExtensionContext.class); + context.getExecutableInvoker().invoke(constructor); } } 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 7c9f0f9d035f..35ae53e9ae89 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 @@ -86,7 +86,7 @@ void reportAndFileEntriesArePublished(Lifecycle lifecycle, int containerEntries, @SuppressWarnings("JUnitMalformedDeclaration") static class MyReportingTestCase { - public MyReportingTestCase(TestReporter reporter) { + MyReportingTestCase(TestReporter reporter) { // Reported on class-level for PER_CLASS lifecycle and on method-level for PER_METHOD lifecycle reporter.publishEntry("Constructor"); reporter.publishFile("constructor", MediaType.TEXT_PLAIN_UTF_8, 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 185f903dc785..c07035f457ef 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 @@ -1163,7 +1163,7 @@ static class ClassTemplateWithDefaultLifecycleAndNestedClassTestCase { @Nested class InnerTestCase { - public InnerTestCase() { + InnerTestCase() { incrementInstanceCount(InnerTestCase.class); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java index a0138398097e..9e711a8d186d 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java @@ -209,7 +209,7 @@ private Stream getEvents(EngineExecutionResults results, InvocationType @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class TestCaseWithThreeInterceptors { - public TestCaseWithThreeInterceptors(TestReporter reporter) { + TestCaseWithThreeInterceptors(TestReporter reporter) { publish(reporter, InvocationType.CONSTRUCTOR); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java index 30f8cb052e21..f15ed3227c23 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java @@ -564,7 +564,7 @@ static class HandlerCallCounter { private int afterEachCalls; private int afterAllCalls; - public HandlerCallCounter() { + HandlerCallCounter() { reset(); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java index 816c505a59da..4a7f2c08323e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java @@ -328,15 +328,15 @@ interface DefaultOrderAndExplicitOrderClassLevelExtensionRegistrationInterface { // @Order(3) @RegisterExtension - static Extension extension1 = new BeforeEachExtension(1); + Extension extension1 = new BeforeEachExtension(1); // @Order(2) @RegisterExtension - static Extension extension2 = new BeforeEachExtension(2); + Extension extension2 = new BeforeEachExtension(2); @Order(1) @RegisterExtension - static Extension extension3 = new BeforeEachExtension(3); + Extension extension3 = new BeforeEachExtension(3); } 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 c871279414ed..2626fc67ec54 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 @@ -431,7 +431,7 @@ void test(String wisdom) { interface ClassLevelExtensionRegistrationInterface { @RegisterExtension - static CrystalBall crystalBall = new CrystalBall("Outlook good"); + CrystalBall crystalBall = new CrystalBall("Outlook good"); @BeforeAll static void beforeAll(String wisdom) { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java index 23750bfc0d4d..f64a970dd9f7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java @@ -128,7 +128,7 @@ static void afterAll() { } // Can be injected into test class constructors if the test class only has @RepeatedTest methods - public LifecycleMethodTests(RepetitionInfo repetitionInfo) { + LifecycleMethodTests(RepetitionInfo repetitionInfo) { assertNotNull(repetitionInfo); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java index 1c3bc1410fe5..15cddad4b2fb 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java @@ -42,7 +42,7 @@ class TestInfoParameterResolverTests extends AbstractJupiterTestEngineTests { private static final List allDisplayNames = Arrays.asList("defaultDisplayName(TestInfo)", "custom display name", "getTags(TestInfo)", "customDisplayNameThatIsEmpty()"); - public TestInfoParameterResolverTests(TestInfo testInfo) { + TestInfoParameterResolverTests(TestInfo testInfo) { assertThat(testInfo.getTestClass()).contains(TestInfoParameterResolverTests.class); assertThat(testInfo.getTestMethod()).isPresent(); } 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 ac92484ef037..4ebed49d5b07 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 @@ -531,11 +531,11 @@ static class MultipleConstructorsTestCase { private final int number; - public MultipleConstructorsTestCase(String text) { + MultipleConstructorsTestCase(String text) { this.number = -1; } - public MultipleConstructorsTestCase(int number) { + MultipleConstructorsTestCase(int number) { this.number = number; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java index 846f8886fc8c..0ebd5c0a1d36 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java @@ -102,7 +102,7 @@ private static class TestClassLoader extends URLClassLoader { private final boolean simulateJUnit4Missing; private final boolean simulateHamcrestMissing; - public TestClassLoader(boolean simulateJUnit4Missing, boolean simulateHamcrestMissing) { + TestClassLoader(boolean simulateJUnit4Missing, boolean simulateHamcrestMissing) { super(CLASSPATH_URLS, getSystemClassLoader()); this.simulateJUnit4Missing = simulateJUnit4Missing; this.simulateHamcrestMissing = simulateHamcrestMissing; 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 1eac5e4d9d01..2e776abf2c69 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 @@ -829,7 +829,7 @@ static class ConstructorInjectionTestCase { private int value; private final TestInfo testInfo; - public ConstructorInjectionTestCase(int value, TestInfo testInfo) { + ConstructorInjectionTestCase(int value, TestInfo testInfo) { this.value = value; this.testInfo = testInfo; } 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 f79252155109..cf042b8ee2ea 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 @@ -2235,7 +2235,7 @@ static class LifecycleTestCase { private static final List lifecycleEvents = new ArrayList<>(); private static final Set testMethods = new LinkedHashSet<>(); - public LifecycleTestCase(TestInfo testInfo) { + LifecycleTestCase(TestInfo testInfo) { lifecycleEvents.add("constructor:" + testInfo.getDisplayName()); } @@ -2524,7 +2524,7 @@ static class ArgumentsAggregatorWithConstructorParameter extends SimpleArguments private final String value; - public ArgumentsAggregatorWithConstructorParameter(String value) { + ArgumentsAggregatorWithConstructorParameter(String value) { this.value = value; } 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 bf9957b9a2d6..1e2605d77e79 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 @@ -239,6 +239,7 @@ static void staticMethod() { final class FinalClass { + @SuppressWarnings({ "FinalMethodInFinalClass", "RedundantModifier" }) final void finalMethod() { } } 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 22022c758fcc..ea00a40b2412 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 @@ -530,7 +530,7 @@ void packageVisibleMethod() { final class FinalClass { - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "FinalMethodInFinalClass", "RedundantModifier" }) final void finalMethod() { } } diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java index 47f857bc6704..b1b513c6900a 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteLauncherDiscoveryRequestBuilderTests.java @@ -656,7 +656,7 @@ private static T exactlyOne(List list) { } private static class StubAbstractTestDescriptor extends AbstractTestDescriptor { - public StubAbstractTestDescriptor() { + StubAbstractTestDescriptor() { super(UniqueId.forEngine("test"), "stub"); } @@ -675,7 +675,7 @@ public Set getTags() { private static class ParentConfigurationParameters implements ConfigurationParameters { private final Map map; - public ParentConfigurationParameters(String key, String value) { + ParentConfigurationParameters(String key, String value) { this.map = Map.of(key, value); } 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 a6922a5d253b..bcfc869d3177 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 @@ -168,7 +168,7 @@ private static class RepeatableAnnotationPredicate extends private final Class annotationType; private final BiPredicate predicate; - public RepeatableAnnotationPredicate(Class annotationType, BiPredicate predicate) { + RepeatableAnnotationPredicate(Class annotationType, BiPredicate predicate) { super("have identical @%s annotation as container annotation", annotationType.getSimpleName()); this.annotationType = annotationType; this.predicate = predicate; diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java index 1a8a30c26adf..0ea59f89f869 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java @@ -39,7 +39,7 @@ void manifestFirst(String module) throws Exception { } // JarInputStream expects the META-INF/MANIFEST.MF to be at the start of the JAR archive - try (final JarInputStream jarInputStream = new JarInputStream(new FileInputStream(modulePath.toFile()))) { + try (var jarInputStream = new JarInputStream(new FileInputStream(modulePath.toFile()))) { assertNotNull(jarInputStream.getManifest(), "MANIFEST.MF should be available via JarInputStream"); } } From 49fab35f32e77838cfb6eb115e7536db4163b025 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 04:33:15 +0000 Subject: [PATCH 079/162] Update dependency com.pinterest.ktlint:ktlint-cli to v1.7.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a1fc28f7b301..e04eb6d346b4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jacoco = "0.8.13" jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" -ktlint = "1.6.0" +ktlint = "1.7.0" log4j = "2.25.1" logback = "1.5.18" opentest4j = "1.3.0" From fa37f355eb13a2e48a46a1a89d4e60f72ecc6f2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 17:57:56 +0000 Subject: [PATCH 080/162] Update plugin develocity to v4.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e04eb6d346b4..a71d064a9824 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -96,7 +96,7 @@ asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-pl 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.3" } -develocity = { id = "com.gradle.develocity", version = "4.0.2" } +develocity = { id = "com.gradle.develocity", version = "4.1" } errorProne = { id = "net.ltgt.errorprone", version = "4.3.0" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "1.0.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" } From d30acddfe6a002f3fa83a36a2b60ebc3452ba9a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:52:50 +0000 Subject: [PATCH 081/162] Update dependency org.apache.maven:apache-maven to v3.9.11 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a71d064a9824..42c4be42e2a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4. kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } -maven = { module = "org.apache.maven:apache-maven", version = "3.9.10" } +maven = { module = "org.apache.maven:apache-maven", version = "3.9.11" } mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" } memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.1" } mockito-bom = { module = "org.mockito:mockito-bom", version = "5.18.0" } From 7c0f6048439cb804d88f6ee8f1ad43ff49d23184 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Jul 2025 18:51:46 +0200 Subject: [PATCH 082/162] Fail on classpath resource names that are blank after removing leading / Fixes #4752. --- .../src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc | 3 ++- .../platform/engine/discovery/ClasspathResourceSelector.java | 3 +++ .../platform/engine/discovery/DiscoverySelectorsTests.java | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc index 25dd429044a1..f098766d77f1 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -26,7 +26,8 @@ repository on GitHub. [[release-notes-5.13.4-junit-platform-bug-fixes]] ==== Bug Fixes -* ❓ +* `ClasspathResourceSelector` no longer allows to be constructed with a resource name that + is blank after removing the leading slash. [[release-notes-5.13.4-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes 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 5cd34a04a990..87c5e6b6bb9b 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 @@ -25,6 +25,7 @@ import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; @@ -64,6 +65,8 @@ public final class ClasspathResourceSelector implements DiscoverySelector { ClasspathResourceSelector(String classpathResourceName, @Nullable FilePosition position) { boolean startsWithSlash = classpathResourceName.startsWith("/"); this.classpathResourceName = (startsWithSlash ? classpathResourceName.substring(1) : classpathResourceName); + Preconditions.notBlank(this.classpathResourceName, + "classpath resource name must not be blank after removing leading slash"); this.position = position; } 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 85ca8917a208..1d8c81a4fbd0 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 @@ -299,8 +299,11 @@ void parseDirectorySelectorWithAbsolutePath() { void selectClasspathResourcesPreconditions() { assertViolatesPrecondition(() -> selectClasspathResource((String) null)); assertViolatesPrecondition(() -> selectClasspathResource("")); + assertViolatesPrecondition(() -> selectClasspathResource("/")); assertViolatesPrecondition(() -> selectClasspathResource(" ")); + assertViolatesPrecondition(() -> selectClasspathResource("/ ")); assertViolatesPrecondition(() -> selectClasspathResource("\t")); + assertViolatesPrecondition(() -> selectClasspathResource("/\t")); assertViolatesPrecondition(() -> selectClasspathResource((Set) null)); assertViolatesPrecondition(() -> selectClasspathResource(Collections.emptySet())); assertViolatesPrecondition(() -> selectClasspathResource(Collections.singleton(null))); From 63362e98c9146c0de2af975b63a43495bb82f1d0 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 13 Jul 2025 13:39:55 +0200 Subject: [PATCH 083/162] Allow default package for `PackageSource` --- .../asciidoc/release-notes/release-notes-5.13.4.adoc | 2 ++ .../engine/support/descriptor/PackageSource.java | 5 ++++- .../engine/support/descriptor/PackageSourceTests.java | 10 +++++++--- .../launcher/core/DiscoveryIssueCollectorTests.java | 1 + 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc index f098766d77f1..54b791e7d66f 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -28,6 +28,8 @@ repository on GitHub. * `ClasspathResourceSelector` no longer allows to be constructed with a resource name that is blank after removing the leading slash. +* `PackageSource.from(String)` now allows to be constructed with an empty string to + indicate the default package. [[release-notes-5.13.4-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java index 702c440eda83..22839c00ffeb 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java @@ -60,7 +60,10 @@ private PackageSource(Package javaPackage) { } private PackageSource(String packageName) { - this.packageName = Preconditions.notBlank(packageName, "package name must not be null or blank"); + Preconditions.notNull(packageName, "package name must not be null"); + Preconditions.condition(packageName.isEmpty() || !packageName.isBlank(), + "package name must not contain only whitespace"); + this.packageName = packageName; } /** 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 f964343c4c2d..7b32ab9cd25a 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 @@ -18,6 +18,8 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; /** @@ -49,9 +51,11 @@ void packageSourceFromNullPackageReference() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from((Package) null)); } - @Test - void packageSourceFromPackageName() { - var testPackage = getClass().getPackage().getName(); + @ParameterizedTest + @ValueSource(classes = PackageSourceTests.class) + @ValueSource(strings = "DefaultPackageTestCase") + void packageSourceFromPackageName(Class testClass) { + var testPackage = testClass.getPackage().getName(); var source = PackageSource.from(testPackage); assertThat(source.getPackageName()).isEqualTo(testPackage); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java index 40994c481f6e..f230fd1dc037 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DiscoveryIssueCollectorTests.java @@ -69,6 +69,7 @@ public static Stream pairs() { new Pair(selectClasspathResource("someResource", FilePosition.from(42, 23)), ClasspathResourceSource.from("someResource", org.junit.platform.engine.support.descriptor.FilePosition.from(42, 23))), // + new Pair(selectPackage(""), PackageSource.from("")), // new Pair(selectPackage("some.package"), PackageSource.from("some.package")), // new Pair(selectFile("someFile"), FileSource.from(new File("someFile"))), // new Pair(selectFile("someFile", FilePosition.from(42)), From 23405b8f88220f803259c5f28ea6220942cd1385 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Jul 2025 18:56:53 +0200 Subject: [PATCH 084/162] Protect against potential problems when converting file-based selectors --- .../core/DiscoveryIssueCollector.java | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) 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 9ec2d1f09abb..e6d8a9ce7c43 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 @@ -10,6 +10,7 @@ package org.junit.platform.launcher.core; +import static org.junit.platform.commons.util.UnrecoverableExceptions.rethrowIfUnrecoverable; import static org.junit.platform.engine.SelectorResolutionResult.Status.FAILED; import static org.junit.platform.engine.SelectorResolutionResult.Status.UNRESOLVED; @@ -78,7 +79,7 @@ else if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelecto } } - static @Nullable TestSource toSource(DiscoverySelector selector) { + private static @Nullable TestSource toSource(DiscoverySelector selector) { if (selector instanceof ClassSelector classSelector) { return ClassSource.from(classSelector.getClassName()); } @@ -96,17 +97,26 @@ else if (result.getStatus() == UNRESOLVED && selector instanceof UniqueIdSelecto if (selector instanceof PackageSelector packageSelector) { return PackageSource.from(packageSelector.getPackageName()); } - if (selector instanceof FileSelector fileSelector) { - return fileSelector.getPosition() // - .map(DiscoveryIssueCollector::convert) // - .map(position -> FileSource.from(fileSelector.getFile(), position)) // - .orElseGet(() -> FileSource.from(fileSelector.getFile())); - } - if (selector instanceof DirectorySelector directorySelector) { - return DirectorySource.from(directorySelector.getDirectory()); + try { + // Both FileSource and DirectorySource call File.getCanonicalFile() to normalize the reported file which + // can throw an exception for certain file names on certain file systems. UriSource.from(...) is affected + // as well because it may return a FileSource or DirectorySource + if (selector instanceof FileSelector fileSelector) { + return fileSelector.getPosition() // + .map(DiscoveryIssueCollector::convert) // + .map(position -> FileSource.from(fileSelector.getFile(), position)) // + .orElseGet(() -> FileSource.from(fileSelector.getFile())); + } + if (selector instanceof DirectorySelector directorySelector) { + return DirectorySource.from(directorySelector.getDirectory()); + } + if (selector instanceof UriSelector uriSelector) { + return UriSource.from(uriSelector.getUri()); + } } - if (selector instanceof UriSelector uriSelector) { - return UriSource.from(uriSelector.getUri()); + catch (Exception ex) { + rethrowIfUnrecoverable(ex); + logger.warn(ex, () -> "Failed to convert DiscoverySelector [%s] into TestSource".formatted(selector)); } return null; } From 91863684d03e504f97ec1fcc576fe4093ba84a58 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 05:32:26 +0000 Subject: [PATCH 085/162] Update dependency io.github.classgraph:classgraph to v4.8.181 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 42c4be42e2a9..cb1f3c7e7f53 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.1" assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } 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.180" } +classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.181" } commons-io = { module = "commons-io:commons-io", version = "2.19.0" } errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.40.0" } fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" } From 2870b7d8fd5bf7c1efe489d3991d3ed3900e82bb Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 16 Jul 2025 08:02:03 +0200 Subject: [PATCH 086/162] Update commons-lang to resolve Dependabot alert for CVE-2025-48924 --- gradle/plugins/publishing/build.gradle.kts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gradle/plugins/publishing/build.gradle.kts b/gradle/plugins/publishing/build.gradle.kts index 67f12269c158..99791df232bd 100644 --- a/gradle/plugins/publishing/build.gradle.kts +++ b/gradle/plugins/publishing/build.gradle.kts @@ -15,6 +15,12 @@ dependencies { } because("Workaround for CVE-2025-4949") } + implementation("org.apache.commons:commons-lang3") { + version { + require("3.18.0") + } + because("Workaround for CVE-2025-48924") + } } } From b0a7077c2ff6ee43bef8bacefe6250dd4678428d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 17 Jul 2025 14:00:26 +0200 Subject: [PATCH 087/162] Work around CVE-2025-48924 for Checkstyle as well --- gradle/libs.versions.toml | 2 -- ...nitbuild.checkstyle-conventions.gradle.kts | 11 ++++++++++ .../junitbuild.checkstyle-nohttp.gradle.kts | 20 +++++++++---------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb1f3c7e7f53..ce36f5710107 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,6 @@ junit4 = "4.13.2" junit4Min = "4.12" ktlint = "1.7.0" log4j = "2.25.1" -logback = "1.5.18" opentest4j = "1.3.0" openTestReporting = "0.2.4" snapshotTests = "1.11.0" @@ -82,7 +81,6 @@ eclipse-platform = { module = "org.eclipse.platform:org.eclipse.platform", versi jacoco = { module = "org.jacoco:jacoco", version.ref = "jacoco" } junit4-latest = { module = "junit:junit", version.ref = "junit4" } junit4-bundle = { module = "org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit", version = "4.13.2_1" } -logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } [bundles] diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts index f207138111fe..eae33aa9151a 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts @@ -5,6 +5,17 @@ plugins { checkstyle } +dependencies { + constraints { + checkstyle("org.apache.commons:commons-lang3") { + version { + require("3.18.0") + } + because("Workaround for CVE-2025-48924") + } + } +} + checkstyle { toolVersion = requiredVersionFromLibs("checkstyle") configDirectory = rootProject.layout.projectDirectory.dir("gradle/config/checkstyle") diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts index b993fedf65fe..324064421b67 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts @@ -7,19 +7,17 @@ plugins { dependencies { checkstyle(dependencyFromLibs("nohttp-checkstyle")) -} - -configurations.checkstyle { - resolutionStrategy { - eachDependency { - // Workaround for CVE-2024-12798 and CVE-2024-12801 - if (requested.group == "ch.qos.logback") { - useVersion(requiredVersionFromLibs("logback")) + constraints { + checkstyle("com.puppycrawl.tools:checkstyle") { + version { + require(requiredVersionFromLibs("checkstyle")) } - // Workaround for CVE-2025-48734 - if (requested.group == "commons-beanutils") { - useVersion("1.11.0") + } + checkstyle("ch.qos.logback:logback-classic") { + version { + require("1.5.18") } + because("Workaround for CVE-2024-12798 and CVE-2024-12801") } } } From 840eea2a23ab782ed1cc73d8635e187d765eea5a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 17 Jul 2025 14:16:16 +0200 Subject: [PATCH 088/162] Remove outdated config --- .../kotlin/junitbuild.spotless-conventions.gradle.kts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts index af3d6818a029..8ae2a9590133 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts @@ -58,16 +58,6 @@ spotless { trimTrailingWhitespace() endWithNewline() } - configurations.named { it.startsWith("spotless") }.configureEach { - // Workaround for CVE-2024-12798 and CVE-2024-12801 - resolutionStrategy { - eachDependency { - if (requested.group == "ch.qos.logback") { - useVersion(requiredVersionFromLibs("logback")) - } - } - } - } } pluginManager.withPlugin("groovy") { From 7cba1ae8aadb1e3c4292fe4c043060bf6fc0b28d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 17 Jul 2025 14:16:43 +0200 Subject: [PATCH 089/162] Forbid redefining repositories in build scripts --- settings.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settings.gradle.kts b/settings.gradle.kts index b797a48cb6db..53dc8d7c4f78 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ import buildparameters.BuildParametersExtension +import org.gradle.api.initialization.resolve.RepositoriesMode.FAIL_ON_PROJECT_REPOS pluginManagement { includeBuild("gradle/plugins") @@ -16,6 +17,7 @@ dependencyResolutionManagement { repositories { mavenCentral() } + repositoriesMode = FAIL_ON_PROJECT_REPOS } val buildParameters = the() From 9ed20d74864974630313e783edb2b060264c8d8b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 9 Jul 2025 11:43:31 +0200 Subject: [PATCH 090/162] Introduce `@Contract` annotation --- ...ld.java-nullability-conventions.gradle.kts | 2 + .../platform/commons/annotation/Contract.java | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts index 98e5b3c1a996..1d09a23998f6 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -54,6 +54,8 @@ tasks.withType().configureEach { enable() } isJSpecifyMode = true + customContractAnnotations.add("org.junit.platform.commons.annotation.Contract") + checkContracts = true } } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java new file mode 100644 index 000000000000..af449e98d524 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.annotation; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * Specifies some aspects of the annotated method's behavior to be used by tools + * for data flow analysis. + * + * @since 6.0 + * @see org.jetbrains.annotations.Contract + * @see NullAway custom contract annotations + */ +@Documented +@Target(ElementType.METHOD) +@API(status = INTERNAL, since = "6.0") +public @interface Contract { + + /** + * Contains the contract clauses describing causal relations between call + * arguments and the returned value. + */ + String value(); + +} From 9794f194ef58b23cbb32b40c0b90036c60c23148 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 9 Jul 2025 13:06:14 +0200 Subject: [PATCH 091/162] Add `@Contract` annotations and simplify code --- .../org/junit/jupiter/api/AssertFalse.java | 4 ++ .../junit/jupiter/api/AssertInstanceOf.java | 4 ++ .../org/junit/jupiter/api/AssertNotNull.java | 4 ++ .../org/junit/jupiter/api/AssertNull.java | 4 ++ .../org/junit/jupiter/api/AssertTrue.java | 4 ++ .../org/junit/jupiter/api/AssertionUtils.java | 6 +++ .../org/junit/jupiter/api/Assertions.java | 21 ++++++++ .../org/junit/jupiter/api/Assumptions.java | 11 ++++ .../extension/ConditionEvaluationResult.java | 2 - .../junit/platform/commons/function/Try.java | 3 ++ .../commons/support/AnnotationSupport.java | 3 ++ .../commons/util/AnnotationUtils.java | 3 ++ .../platform/commons/util/ExceptionUtils.java | 3 ++ .../platform/commons/util/Preconditions.java | 52 +++++++++---------- .../platform/commons/util/StringUtils.java | 9 ++++ .../platform/console/tasks/TreeNode.java | 5 +- .../platform/console/tasks/TreePrinter.java | 3 +- .../reporting/legacy/xml/XmlReportWriter.java | 3 +- .../descriptor/VintageTestDescriptor.java | 1 - 19 files changed, 108 insertions(+), 37 deletions(-) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java index ea248480a299..d0e308d3be26 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertFalse} is a collection of utility methods that support asserting @@ -29,16 +30,19 @@ private AssertFalse() { /* no-op */ } + @Contract("true -> fail") static void assertFalse(boolean condition) { assertFalse(condition, (String) null); } + @Contract("true, _ -> fail") static void assertFalse(boolean condition, @Nullable String message) { if (condition) { failNotFalse(message); } } + @Contract("true, _ -> fail") static void assertFalse(boolean condition, Supplier<@Nullable String> messageSupplier) { if (condition) { failNotFalse(messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java index 02f72aeffdc3..9586d350f7a8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertInstanceOf} is a collection of utility methods that support @@ -29,14 +30,17 @@ private AssertInstanceOf() { /* no-op */ } + @Contract("_, null -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return assertInstanceOf(expectedType, actualValue, (Object) null); } + @Contract("_, null, _ -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return assertInstanceOf(expectedType, actualValue, (Object) message); } + @Contract("_, null, _ -> fail") static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, Supplier<@Nullable String> messageSupplier) { return assertInstanceOf(expectedType, actualValue, (Object) messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java index 1f1208f297dc..a03079f49341 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertNotNull} is a collection of utility methods that support asserting @@ -28,16 +29,19 @@ private AssertNotNull() { /* no-op */ } + @Contract("null -> fail") static void assertNotNull(@Nullable Object actual) { assertNotNull(actual, (String) null); } + @Contract("null, _ -> fail") static void assertNotNull(@Nullable Object actual, @Nullable String message) { if (actual == null) { failNull(message); } } + @Contract("null, _ -> fail") static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual == null) { failNull(messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java index 1acf4c1e6154..137fb1e83aee 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertNull} is a collection of utility methods that support asserting @@ -28,16 +29,19 @@ private AssertNull() { /* no-op */ } + @Contract("!null -> fail") static void assertNull(@Nullable Object actual) { assertNull(actual, (String) null); } + @Contract("!null, _ -> fail") static void assertNull(@Nullable Object actual, @Nullable String message) { if (actual != null) { failNotNull(actual, message); } } + @Contract("!null, _ -> fail") static void assertNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { if (actual != null) { failNotNull(actual, messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java index c106e08995e5..cb92e2278419 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * {@code AssertTrue} is a collection of utility methods that support asserting @@ -29,16 +30,19 @@ private AssertTrue() { /* no-op */ } + @Contract("false -> fail") static void assertTrue(boolean condition) { assertTrue(condition, (String) null); } + @Contract("false, _ -> fail") static void assertTrue(boolean condition, @Nullable String message) { if (!condition) { failNotTrue(message); } } + @Contract("false, _ -> fail") static void assertTrue(boolean condition, Supplier<@Nullable String> messageSupplier) { if (!condition) { failNotTrue(messageSupplier); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java index 1d18b446f2f9..fb8f052bf2b5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java @@ -16,6 +16,7 @@ import java.util.function.Supplier; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.opentest4j.AssertionFailedError; @@ -31,22 +32,27 @@ private AssertionUtils() { /* no-op */ } + @Contract(" -> fail") static void fail() { throw new AssertionFailedError(); } + @Contract("_ -> fail") static void fail(@Nullable String message) { throw new AssertionFailedError(message); } + @Contract("_, _ -> fail") static void fail(@Nullable String message, @Nullable Throwable cause) { throw new AssertionFailedError(message, cause); } + @Contract("_ -> fail") static void fail(@Nullable Throwable cause) { throw new AssertionFailedError(null, cause); } + @Contract("_ -> fail") static void fail(Supplier<@Nullable String> messageSupplier) { throw new AssertionFailedError(nullSafeGet(messageSupplier)); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java index b551f44de874..2a4ac5682840 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 @@ -25,6 +25,7 @@ import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.ThrowingSupplier; +import org.junit.platform.commons.annotation.Contract; import org.opentest4j.MultipleFailuresError; /** @@ -115,6 +116,7 @@ protected Assertions() { *

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

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

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

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

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

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

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

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

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

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

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

    If necessary, the failure message will be retrieved lazily from the supplied {@code messageSupplier}. */ + @Contract("null, _ -> fail") public static void assertNotNull(@Nullable Object actual, Supplier<@Nullable String> messageSupplier) { AssertNotNull.assertNotNull(actual, messageSupplier); } @@ -3665,6 +3683,7 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * @since 5.8 */ @API(status = STABLE, since = "5.10") + @Contract("_, null -> fail") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue); } @@ -3681,6 +3700,7 @@ public static T assertInstanceOf(Class expectedType, @Nullable Object act * @since 5.8 */ @API(status = STABLE, since = "5.10") + @Contract("_, null, _ -> fail") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, @Nullable String message) { return AssertInstanceOf.assertInstanceOf(expectedType, actualValue, message); @@ -3698,6 +3718,7 @@ public static T assertInstanceOf(Class expectedType, @Nullable Object act * * @since 5.8 */ + @Contract("_, null, _ -> fail") @API(status = STABLE, since = "5.10") public static T assertInstanceOf(Class expectedType, @Nullable Object actualValue, Supplier<@Nullable String> messageSupplier) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java index 4e293a9fee4b..572f14b9de25 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java @@ -18,6 +18,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.function.Executable; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; import org.opentest4j.TestAbortedException; @@ -68,6 +69,7 @@ protected Assumptions() { * @param assumption the assumption to validate * @throws TestAbortedException if the assumption is not {@code true} */ + @Contract("false -> fail") public static void assumeTrue(boolean assumption) throws TestAbortedException { assumeTrue(assumption, "assumption is not true"); } @@ -103,6 +105,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, @Nullable Stri * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ + @Contract("false, _ -> fail") public static void assumeTrue(boolean assumption, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { if (!assumption) { @@ -118,6 +121,7 @@ public static void assumeTrue(boolean assumption, Supplier<@Nullable String> mes * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code true} */ + @Contract("false, _ -> fail") public static void assumeTrue(boolean assumption, @Nullable String message) throws TestAbortedException { if (!assumption) { throwAssumptionFailed(message); @@ -146,6 +150,7 @@ public static void assumeTrue(BooleanSupplier assumptionSupplier, Supplier<@Null * @param assumption the assumption to validate * @throws TestAbortedException if the assumption is not {@code false} */ + @Contract("true -> fail") public static void assumeFalse(boolean assumption) throws TestAbortedException { assumeFalse(assumption, "assumption is not false"); } @@ -181,6 +186,7 @@ public static void assumeFalse(BooleanSupplier assumptionSupplier, @Nullable Str * the {@code TestAbortedException} if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ + @Contract("true, _ -> fail") public static void assumeFalse(boolean assumption, Supplier<@Nullable String> messageSupplier) throws TestAbortedException { if (assumption) { @@ -196,6 +202,7 @@ public static void assumeFalse(boolean assumption, Supplier<@Nullable String> me * if the assumption is invalid * @throws TestAbortedException if the assumption is not {@code false} */ + @Contract("true, _ -> fail") public static void assumeFalse(boolean assumption, @Nullable String message) throws TestAbortedException { if (assumption) { throwAssumptionFailed(message); @@ -277,6 +284,7 @@ public static void assumingThat(boolean assumption, Executable executable) { * @throws TestAbortedException always * @since 5.9 */ + @Contract(" -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort() { @@ -301,6 +309,7 @@ public static V abort() { * @throws TestAbortedException always * @since 5.9 */ + @Contract("_ -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(String message) { @@ -318,12 +327,14 @@ public static V abort(String message) { * @throws TestAbortedException always * @since 5.9 */ + @Contract("_ -> fail") @API(status = STABLE, since = "5.9") @SuppressWarnings("TypeParameterUnusedInFormals") public static V abort(Supplier messageSupplier) { throw new TestAbortedException(messageSupplier.get()); } + @Contract("_ -> fail") private static void throwAssumptionFailed(@Nullable String message) { throw new TestAbortedException( StringUtils.isNotBlank(message) ? "Assumption failed: " + message : "Assumption failed"); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index c4de9b5bcd84..dbdb8539ebf1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -70,7 +70,6 @@ public static ConditionEvaluationResult disabled(@Nullable String reason) { * @see StringUtils#isBlank(String) */ @API(status = STABLE, since = "5.7") - @SuppressWarnings("NullAway") // StringUtils.isBlank() does not yet have a nullability @Contract public static ConditionEvaluationResult disabled(@Nullable String reason, @Nullable String customReason) { if (StringUtils.isBlank(reason)) { return disabled(customReason); @@ -85,7 +84,6 @@ public static ConditionEvaluationResult disabled(@Nullable String reason, @Nulla private final Optional reason; - @SuppressWarnings("NullAway") // StringUtils.isNotBlank() does not yet have a nullability @Contract private ConditionEvaluationResult(boolean enabled, @Nullable String reason) { this.enabled = enabled; this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java index 7ac71abb59dc..9cb26236e463 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java @@ -24,6 +24,7 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.annotation.Contract; /** * A container object which may either contain a nullable value in case of @@ -364,11 +365,13 @@ public Try orElse(Supplier> supplier) { return Try.of(supplier::get); } + @Contract(" -> fail") @Override public V get() throws Exception { throw this.cause; } + @Contract("_ -> fail") @Override public V getOrThrow(Function exceptionTransformer) throws E { checkNotNull(exceptionTransformer, "exceptionTransformer"); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java index 97e245989f26..32bd731b217e 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java @@ -24,6 +24,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; @@ -70,6 +71,7 @@ private AnnotationSupport() { * @see #findRepeatableAnnotations(Optional, Class) */ @API(status = MAINTAINED, since = "1.3") + @Contract("null, _ -> false") @SuppressWarnings("NullableOptional") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { @@ -94,6 +96,7 @@ public static boolean isAnnotated(@Nullable Optional * @see #findAnnotation(AnnotatedElement, Class) * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ + @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java index fcf4ece0978a..be312dc7986d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java @@ -38,6 +38,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.annotation.Contract; import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; /** @@ -76,6 +77,7 @@ private AnnotationUtils() { * @see org.junit.platform.commons.support.AnnotationSupport#isAnnotated(Optional, Class) */ @SuppressWarnings("NullableOptional") + @Contract("null, _ -> false") public static boolean isAnnotated(@Nullable Optional element, Class annotationType) { @@ -102,6 +104,7 @@ public static boolean isAnnotated(Parameter parameter, int index, Class false") public static boolean isAnnotated(@Nullable AnnotatedElement element, Class annotationType) { return findAnnotation(element, annotationType).isPresent(); } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java index 3f68495d2393..cb20454d6fc1 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java @@ -25,6 +25,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for working with exceptions. @@ -69,6 +70,7 @@ private ExceptionUtils() { * returns anything; the return type is merely present to allow this * method to be supplied as the operand in a {@code throw} statement */ + @Contract("_ -> fail") public static RuntimeException throwAsUncheckedException(Throwable t) { Preconditions.notNull(t, "Throwable must not be null"); // The following line will never actually return an exception but rather @@ -76,6 +78,7 @@ public static RuntimeException throwAsUncheckedException(Throwable t) { return ExceptionUtils.throwAs(t); } + @Contract("_ -> fail") @SuppressWarnings({ "unchecked", "TypeParameterUnusedInFormals" }) private static T throwAs(Throwable t) throws T { throw (T) t; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java index c5e3e502c7c7..7032f0d739d7 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java @@ -19,6 +19,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for asserting preconditions for method and @@ -51,10 +52,9 @@ private Preconditions() { * @throws PreconditionViolationException if the supplied object is {@code null} * @see #notNull(Object, Supplier) */ + @Contract("null, _ -> fail; !null, _ -> param1") public static T notNull(@Nullable T object, String message) throws PreconditionViolationException { - if (object == null) { - throw new PreconditionViolationException(message); - } + condition(object != null, message); return object; } @@ -67,12 +67,11 @@ public static T notNull(@Nullable T object, String message) throws Precondit * @throws PreconditionViolationException if the supplied object is {@code null} * @see #condition(boolean, Supplier) */ + @Contract("null, _ -> fail; !null, _ -> param1") public static T notNull(@Nullable T object, Supplier messageSupplier) throws PreconditionViolationException { - if (object == null) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(object != null, messageSupplier); return object; } @@ -87,11 +86,10 @@ public static T notNull(@Nullable T object, Supplier messageSupplier * @since 1.9 * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") @API(status = INTERNAL, since = "1.11") public static int[] notEmpty(int @Nullable [] array, String message) throws PreconditionViolationException { - if (array == null || array.length == 0) { - throw new PreconditionViolationException(message); - } + condition(array != null && array.length > 0, message); return array; } @@ -109,10 +107,9 @@ public static int[] notEmpty(int @Nullable [] array, String message) throws Prec * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static T[] notEmpty(T @Nullable [] array, String message) throws PreconditionViolationException { - if (array == null || array.length == 0) { - throw new PreconditionViolationException(message); - } + condition(array != null && array.length > 0, message); return array; } @@ -130,12 +127,11 @@ public static T[] notEmpty(T @Nullable [] array, String message) throws Prec * @see #containsNoNullElements(Object[], String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static T[] notEmpty(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { - if (array == null || array.length == 0) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(array != null && array.length > 0, messageSupplier); return array; } @@ -152,12 +148,11 @@ public static T[] notEmpty(T @Nullable [] array, Supplier messageSup * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static > T notEmpty(@Nullable T collection, String message) throws PreconditionViolationException { - if (collection == null || collection.isEmpty()) { - throw new PreconditionViolationException(message); - } + condition(collection != null && !collection.isEmpty(), message); return collection; } @@ -174,12 +169,11 @@ public static > T notEmpty(@Nullable T collection, Strin * @see #containsNoNullElements(Collection, String) * @see #condition(boolean, String) */ + @Contract("null, _ -> fail") public static > T notEmpty(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { - if (collection == null || collection.isEmpty()) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(collection != null && !collection.isEmpty(), messageSupplier); return collection; } @@ -197,6 +191,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static T @Nullable [] containsNoNullElements(T @Nullable [] array, String message) throws PreconditionViolationException { @@ -219,6 +214,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * any {@code null} elements * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static T @Nullable [] containsNoNullElements(T @Nullable [] array, Supplier messageSupplier) throws PreconditionViolationException { @@ -241,6 +237,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * any {@code null} elements * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static > @Nullable T containsNoNullElements(@Nullable T collection, String message) throws PreconditionViolationException { @@ -263,6 +260,7 @@ public static > T notEmpty(@Nullable T collection, Suppl * any {@code null} elements * @see #notNull(Object, String) */ + @Contract("null, _ -> null") public static > @Nullable T containsNoNullElements(@Nullable T collection, Supplier messageSupplier) throws PreconditionViolationException { @@ -284,10 +282,9 @@ public static > T notEmpty(@Nullable T collection, Suppl * @throws PreconditionViolationException if the supplied string is blank * @see #notBlank(String, Supplier) */ + @Contract("null, _ -> fail") public static String notBlank(@Nullable String str, String message) throws PreconditionViolationException { - if (str == null || StringUtils.isBlank(str)) { - throw new PreconditionViolationException(message); - } + condition(StringUtils.isNotBlank(str), message); return str; } @@ -304,12 +301,11 @@ public static String notBlank(@Nullable String str, String message) throws Preco * @see StringUtils#isNotBlank(String) * @see #condition(boolean, Supplier) */ + @Contract("null, _ -> fail") public static String notBlank(@Nullable String str, Supplier messageSupplier) throws PreconditionViolationException { - if (str == null || StringUtils.isBlank(str)) { - throw new PreconditionViolationException(messageSupplier.get()); - } + condition(StringUtils.isNotBlank(str), messageSupplier); return str; } @@ -321,6 +317,7 @@ public static String notBlank(@Nullable String str, Supplier messageSupp * @throws PreconditionViolationException if the predicate is {@code false} * @see #condition(boolean, Supplier) */ + @Contract("false, _ -> fail") public static void condition(boolean predicate, String message) throws PreconditionViolationException { if (!predicate) { throw new PreconditionViolationException(message); @@ -334,6 +331,7 @@ public static void condition(boolean predicate, String message) throws Precondit * @param messageSupplier precondition violation message supplier * @throws PreconditionViolationException if the predicate is {@code false} */ + @Contract("false, _ -> fail") public static void condition(boolean predicate, Supplier messageSupplier) throws PreconditionViolationException { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java index d20052a7bf2f..1fd222935030 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java @@ -21,6 +21,7 @@ import org.apiguardian.api.API; import org.jspecify.annotations.Nullable; +import org.junit.platform.commons.annotation.Contract; /** * Collection of utilities for working with {@link String Strings}, @@ -69,6 +70,7 @@ private StringUtils() { * @see String#isBlank() * @see #isNotBlank(String) */ + @Contract("null -> true") public static boolean isBlank(@Nullable String str) { return (str == null || str.isBlank()); } @@ -81,6 +83,7 @@ public static boolean isBlank(@Nullable String str) { * @return {@code true} if the string is not blank * @see #isBlank(String) */ + @Contract("null -> false") public static boolean isNotBlank(@Nullable String str) { return !isBlank(str); } @@ -93,6 +96,7 @@ public static boolean isNotBlank(@Nullable String str) { * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ + @Contract("null -> false") public static boolean containsWhitespace(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isWhitespace); } @@ -107,6 +111,7 @@ public static boolean containsWhitespace(@Nullable String str) { * @see #containsIsoControlCharacter(String) * @see Character#isWhitespace(int) */ + @Contract("null -> true") public static boolean doesNotContainWhitespace(@Nullable String str) { return !containsWhitespace(str); } @@ -119,6 +124,7 @@ public static boolean doesNotContainWhitespace(@Nullable String str) { * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ + @Contract("null -> false") public static boolean containsIsoControlCharacter(@Nullable String str) { return str != null && str.codePoints().anyMatch(Character::isISOControl); } @@ -133,6 +139,7 @@ public static boolean containsIsoControlCharacter(@Nullable String str) { * @see #containsWhitespace(String) * @see Character#isISOControl(int) */ + @Contract("null -> true") public static boolean doesNotContainIsoControlCharacter(@Nullable String str) { return !containsIsoControlCharacter(str); } @@ -241,6 +248,7 @@ public static String defaultToString(@Nullable Object obj) { * @since 1.4 */ @API(status = INTERNAL, since = "1.4") + @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable String replaceIsoControlCharacters(@Nullable String str, String replacement) { Preconditions.notNull(replacement, "replacement must not be null"); return str == null ? null : ISO_CONTROL_PATTERN.matcher(str).replaceAll(replacement); @@ -256,6 +264,7 @@ public static String defaultToString(@Nullable Object obj) { * @since 1.4 */ @API(status = INTERNAL, since = "1.4") + @Contract("null, _ -> null; !null, _ -> !null") public static @Nullable String replaceWhitespaceCharacters(@Nullable String str, String replacement) { Preconditions.notNull(replacement, "replacement must not be null"); return str == null ? null : WHITESPACE_PATTERN.matcher(str).replaceAll(replacement); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java index 03de24ef6bd4..6858069246a2 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java @@ -10,8 +10,6 @@ package org.junit.platform.console.tasks; -import static java.util.Objects.requireNonNull; - import java.util.Optional; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -97,10 +95,11 @@ Optional identifier() { return Optional.ofNullable(identifier); } + @SuppressWarnings("DataFlowIssue") static String createCaption(String displayName) { boolean normal = displayName.length() <= 80; String caption = normal ? displayName : displayName.substring(0, 80) + "..."; String whites = StringUtils.replaceWhitespaceCharacters(caption, " "); - return requireNonNull(StringUtils.replaceIsoControlCharacters(whites, ".")); + return StringUtils.replaceIsoControlCharacters(whites, "."); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java index f6df0f45c825..edf14e43a249 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java @@ -10,7 +10,6 @@ package org.junit.platform.console.tasks; -import static java.util.Objects.requireNonNull; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; import java.io.PrintWriter; @@ -129,7 +128,7 @@ private void printThrowable(String indent, TestExecutionResult result) { if (StringUtils.isBlank(message)) { message = throwable.toString(); } - printMessage(Style.FAILED, indent, requireNonNull(message)); + printMessage(Style.FAILED, indent, message); } private void printReportEntry(String indent, ReportEntry reportEntry) { diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java index d9e11b931e46..9b0d216a2d59 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java @@ -14,7 +14,6 @@ import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static java.util.Collections.emptyList; import static java.util.Comparator.naturalOrder; -import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; @@ -244,7 +243,7 @@ private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, private void writeSkippedElement(@Nullable String reason, XMLStreamWriter writer) throws XMLStreamException { if (isNotBlank(reason)) { writer.writeStartElement("skipped"); - writeCDataSafely(requireNonNull(reason)); + writeCDataSafely(reason); writer.writeEndElement(); } else { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java index d9ad9ea0ac73..638eb2f0761a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java @@ -55,7 +55,6 @@ public VintageTestDescriptor(UniqueId uniqueId, Description description, @Nullab this.description = description; } - @SuppressWarnings("NullAway") private static String generateDisplayName(Description description) { String methodName = DescriptionUtils.getMethodName(description); return isNotBlank(methodName) ? methodName : description.getDisplayName(); From da9cf72c906337dd3a6ec0d88407a6869ab76c91 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 12 Jul 2025 13:23:44 +0200 Subject: [PATCH 092/162] Add `computeIfAbsent` variants to stores for non-nullable types Since both `ExtensionContext.Store` and its backing `NamespaceAwareStore` implementation allow storing `null` values, the `getOrComputeIfAbsent` methods potentially returned `null`, even if documented otherwise (in case of `getOrComputeIfAbsent(Class)` in `ExtensionContext.Store`). Now that the API uses JSpecify's nullability annotations, this became more apparent and also caused Jupiter `Extension` implementations to have to deal with values being potentially `null` despite their best intentions to only use `getOrComputeIfAbsent` to access a certain `key` with a function that never returned `null`. Therefore, this commit introduces null-safe variants called `computeIfAbsent` that treat `null` as absent in addition to the key not being present in the backing `Map`. --- .../release-notes/release-notes-6.0.0-M2.adoc | 8 + .../test/java/example/FirstCustomEngine.java | 5 +- .../test/java/example/SecondCustomEngine.java | 5 +- .../extensions/HttpServerExtension.java | 5 +- .../session/GlobalSetupTeardownListener.java | 2 +- .../api/extension/ExtensionContext.java | 155 ++++++++++++++++-- .../jupiter/api/fixtures/TrackLogRecords.java | 4 +- .../engine/execution/NamespaceAwareStore.java | 33 +++- .../engine/extension/TempDirectory.java | 9 +- .../engine/extension/TimeoutExtension.java | 5 +- .../extension/TimeoutInvocationFactory.java | 4 +- .../rules/ExpectedExceptionSupport.java | 4 +- .../rules/TestRuleSupport.java | 5 +- .../params/ArgumentCountValidator.java | 6 +- .../store/NamespacedHierarchicalStore.java | 81 ++++++++- .../jupiter/api/extension/Heavyweight.java | 3 +- .../descriptor/ExtensionContextTests.java | 9 +- ...ExtensionContextStoreConcurrencyTests.java | 2 +- .../execution/ExtensionContextStoreTests.java | 11 ++ .../ExtensionContextExecutionTests.java | 2 +- .../TimeoutInvocationFactoryTests.java | 3 +- .../NamespacedHierarchicalStoreTests.java | 130 +++++++++++++-- .../tooling/support/tests/LocalMavenRepo.java | 2 +- .../support/tests/ManagedResource.java | 2 +- .../tests/OutputAttachingExtension.java | 2 +- 25 files changed, 410 insertions(+), 87 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 1e78b05dca56..ba7b708eea7d 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -29,6 +29,8 @@ repository on GitHub. * `ConversionSupport` now converts `String` to `Locale` using the IETF BCP 47 language tag format supported by the `Locale.forLanguageTag(String)` factory method instead of the format used by the deprecated `Locale(String)` constructor. +* Deprecate `getOrComputeIfAbsent(...)` methods in `NamespacedHierarchicalStore` in favor + of the new `computeIfAbsent(...)` methods. [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements @@ -51,6 +53,8 @@ repository on GitHub. * Provide cancellation support for the Suite and Vintage test engines * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. +* Introduce `computeIfAbsent(...)` methods in `NamespacedHierarchicalStore` to simplify + working with non-nullable types. [[release-notes-6.0.0-M2-junit-jupiter]] @@ -72,12 +76,16 @@ repository on GitHub. configuration parameter. `Locale` conversions are now always performed using the IETF BCP 47 language tag format supported by the `Locale.forLanguageTag(String)` factory method. +* Deprecate `getOrComputeIfAbsent(...)` methods in `ExtensionContext.Store` in favor of + the new `computeIfAbsent(...)` methods. [[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements * Reason strings supplied to `ConditionEvaluationResult` APIs are now officially declared as `@Nullable`. +* Introduce `computeIfAbsent(...)` methods in `ExtensionContext.Store` to ease simplify + with non-nullable types. [[release-notes-6.0.0-M2-junit-vintage]] diff --git a/documentation/src/test/java/example/FirstCustomEngine.java b/documentation/src/test/java/example/FirstCustomEngine.java index 46c66f926540..3d84cea7370c 100644 --- a/documentation/src/test/java/example/FirstCustomEngine.java +++ b/documentation/src/test/java/example/FirstCustomEngine.java @@ -48,9 +48,6 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId return new EngineDescriptor(uniqueId, "First Custom Test Engine"); } - //end::user_guide[] - @SuppressWarnings("NullAway") - //tag::user_guide[] @Override public void execute(ExecutionRequest request) { request.getEngineExecutionListener() @@ -58,7 +55,7 @@ public void execute(ExecutionRequest request) { .executionStarted(request.getRootTestDescriptor()); NamespacedHierarchicalStore store = request.getStore(); - socket = store.getOrComputeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> { + socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> { try { return new ServerSocket(0, 50, getLoopbackAddress()); } diff --git a/documentation/src/test/java/example/SecondCustomEngine.java b/documentation/src/test/java/example/SecondCustomEngine.java index f41223d6dfcc..c6f11182f9d1 100644 --- a/documentation/src/test/java/example/SecondCustomEngine.java +++ b/documentation/src/test/java/example/SecondCustomEngine.java @@ -48,9 +48,6 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId return new EngineDescriptor(uniqueId, "Second Custom Test Engine"); } - //end::user_guide[] - @SuppressWarnings("NullAway") - //tag::user_guide[] @Override public void execute(ExecutionRequest request) { request.getEngineExecutionListener() @@ -58,7 +55,7 @@ public void execute(ExecutionRequest request) { .executionStarted(request.getRootTestDescriptor()); NamespacedHierarchicalStore store = request.getStore(); - socket = store.getOrComputeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> { + socket = store.computeIfAbsent(Namespace.GLOBAL, "serverSocket", key -> { try { return new ServerSocket(0, 50, getLoopbackAddress()); } diff --git a/documentation/src/test/java/example/extensions/HttpServerExtension.java b/documentation/src/test/java/example/extensions/HttpServerExtension.java index 33a447468790..cc9947d6f060 100644 --- a/documentation/src/test/java/example/extensions/HttpServerExtension.java +++ b/documentation/src/test/java/example/extensions/HttpServerExtension.java @@ -28,16 +28,13 @@ 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); Class key = HttpServerResource.class; - HttpServerResource resource = store.getOrComputeIfAbsent(key, __ -> { + HttpServerResource resource = store.computeIfAbsent(key, __ -> { try { HttpServerResource serverResource = new HttpServerResource(0); serverResource.start(); diff --git a/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java index fdddad84ea4e..bc79e04cbba8 100644 --- a/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java +++ b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java @@ -43,7 +43,7 @@ public void testPlanExecutionStarted(TestPlan testPlan) { } //tag::user_guide[] NamespacedHierarchicalStore store = session.getStore(); // <1> - store.getOrComputeIfAbsent(Namespace.GLOBAL, "httpServer", key -> { // <2> + store.computeIfAbsent(Namespace.GLOBAL, "httpServer", key -> { // <2> InetSocketAddress address = new InetSocketAddress(getLoopbackAddress(), 0); HttpServer server; try { 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 086a89f8d37d..cc52c99f266d 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 @@ -569,10 +569,9 @@ interface CloseableResource { * * @param key the key; never {@code null} * @param requiredType the required type of the value; never {@code null} - * @param defaultValue the default value + * @param defaultValue the default value; never {@code null} * @param the value type - * @return the value; potentially {@code null} if {@code defaultValue} - * is {@code null} + * @return the value; never {@code null} * @since 5.5 * @see #get(Object, Class) */ @@ -592,13 +591,13 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * the type of object we wish to retrieve from the store. * *

    -		 * X x = store.getOrComputeIfAbsent(X.class, key -> new X(), X.class);
    +		 * X x = store.computeIfAbsent(X.class, key -> new X(), X.class);
     		 * // Equivalent to:
    -		 * // X x = store.getOrComputeIfAbsent(X.class);
    +		 * // X x = store.computeIfAbsent(X.class);
     		 * 
    * - *

    See {@link #getOrComputeIfAbsent(Object, Function, Class)} for - * further details. + *

    See {@link #computeIfAbsent(Object, Function, Class)} for further + * details. * *

    If {@code type} implements {@link CloseableResource} or * {@link AutoCloseable} (unless the @@ -610,14 +609,56 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * @param the key and value type * @return the object; never {@code null} * @since 5.1 - * @see #getOrComputeIfAbsent(Object, Function) - * @see #getOrComputeIfAbsent(Object, Function, Class) + * @see #computeIfAbsent(Class) + * @see #computeIfAbsent(Object, Function) + * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable + * + * @deprecated Please use {@link #computeIfAbsent(Class)} instead. */ - @API(status = STABLE, since = "5.1") - default @Nullable V getOrComputeIfAbsent(Class type) { - return getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); + @Deprecated + @API(status = DEPRECATED, since = "6.0") + default V getOrComputeIfAbsent(Class type) { + return computeIfAbsent(type); + } + + /** + * Return the object of type {@code type} if it is present and not + * {@code null} in this {@code Store} (keyed by {@code type}); + * otherwise, invoke the default constructor for {@code type} to + * generate the object, store it, and return it. + * + *

    This method is a shortcut for the following, where {@code X} is + * the type of object we wish to retrieve from the store. + * + *

    +		 * X x = store.computeIfAbsent(X.class, key -> new X(), X.class);
    +		 * // Equivalent to:
    +		 * // X x = store.computeIfAbsent(X.class);
    +		 * 
    + * + *

    See {@link #computeIfAbsent(Object, Function, Class)} for further + * details. + * + *

    If {@code type} implements {@link CloseableResource} or + * {@link AutoCloseable} (unless the + * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} + * configuration parameter is set to {@code false}), then the {@code close()} + * method will be invoked on the stored object when the store is closed. + * + * @param type the type of object to retrieve; never {@code null} + * @param the key and value type + * @return the object; never {@code null} + * @since 6.0 + * @see #computeIfAbsent(Object, Function) + * @see #computeIfAbsent(Object, Function, Class) + * @see CloseableResource + * @see AutoCloseable + */ + @API(status = MAINTAINED, since = "6.0") + default V computeIfAbsent(Class type) { + return computeIfAbsent(type, ReflectionSupport::newInstance, type); } /** @@ -631,7 +672,7 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * the {@code key} as input), stored, and returned. * *

    For greater type safety, consider using - * {@link #getOrComputeIfAbsent(Object, Function, Class)} instead. + * {@link #computeIfAbsent(Object, Function, Class)} instead. * *

    If the created value is an instance of {@link CloseableResource} or * {@link AutoCloseable} (unless the @@ -645,14 +686,56 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * @param the key type * @param the value type * @return the value; potentially {@code null} - * @see #getOrComputeIfAbsent(Class) - * @see #getOrComputeIfAbsent(Object, Function, Class) + * @see #computeIfAbsent(Class) + * @see #computeIfAbsent(Object, Function) + * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable + * + * @deprecated Please use {@link #computeIfAbsent(Object, Function)} + * instead. */ + @Deprecated + @API(status = DEPRECATED, since = "6.0") @Nullable Object getOrComputeIfAbsent(K key, Function defaultCreator); + /** + * Return the value of the specified required type that is stored under + * the supplied {@code key}. + * + *

    If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. If no value is found for the supplied {@code key} + * or the value is {@code null}, a new value will be computed by the + * {@code defaultCreator} (given the {@code key} as input), stored, and + * returned. + * + *

    For greater type safety, consider using + * {@link #computeIfAbsent(Object, Function, Class)} instead. + * + *

    If the created value is an instance of {@link CloseableResource} or + * {@link AutoCloseable} (unless the + * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} + * configuration parameter is set to {@code false}), then the {@code close()} + * method will be invoked on the stored object when the store is closed. + * + * @param key the key; never {@code null} + * @param defaultCreator the function called with the supplied {@code key} + * to create a new value; never {@code null} and must not return + * {@code null} + * @param the key type + * @param the value type + * @return the value; never {@code null} + * @since 6.0 + * @see #computeIfAbsent(Class) + * @see #computeIfAbsent(Object, Function, Class) + * @see CloseableResource + * @see AutoCloseable + */ + Object computeIfAbsent(K key, Function defaultCreator); + /** * Get the value of the specified required type that is stored under the * supplied {@code key}. @@ -664,7 +747,7 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * a new value will be computed by the {@code defaultCreator} (given * the {@code key} as input), stored, and returned. * - *

    If {@code requiredType} implements {@link CloseableResource} or + *

    If the created value implements {@link CloseableResource} or * {@link AutoCloseable} (unless the * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} * configuration parameter is set to {@code false}), then the {@code close()} @@ -677,14 +760,50 @@ default V getOrDefault(Object key, Class requiredType, V defaultValue) { * @param the key type * @param the value type * @return the value; potentially {@code null} - * @see #getOrComputeIfAbsent(Class) - * @see #getOrComputeIfAbsent(Object, Function) + * @see #computeIfAbsent(Class) + * @see #computeIfAbsent(Object, Function) + * @see #computeIfAbsent(Object, Function, Class) * @see CloseableResource * @see AutoCloseable */ + @Deprecated + @API(status = DEPRECATED, since = "6.0") @Nullable V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType); + /** + * Get the value of the specified required type that is stored under the + * supplied {@code key}. + * + *

    If no value is stored in the current {@link ExtensionContext} + * for the supplied {@code key}, ancestors of the context will be queried + * for a value with the same {@code key} in the {@code Namespace} used + * to create this store. If no value is found for the supplied {@code key} + * or the value is {@code null}, a new value will be computed by the + * {@code defaultCreator} (given the {@code key} as input), stored, and + * returned. + * + *

    If the created value is an instance of {@link CloseableResource} or + * {@link AutoCloseable} (unless the + * {@code junit.jupiter.extensions.store.close.autocloseable.enabled} + * configuration parameter is set to {@code false}), then the {@code close()} + * method will be invoked on the stored object when the store is closed. + * + * @param key the key; never {@code null} + * @param defaultCreator the function called with the supplied {@code key} + * to create a new value; never {@code null} and must not return + * {@code null} + * @param requiredType the required type of the value; never {@code null} + * @param the key type + * @param the value type + * @return the value; never {@code null} + * @see #computeIfAbsent(Class) + * @see #computeIfAbsent(Object, Function) + * @see CloseableResource + * @see AutoCloseable + */ + V computeIfAbsent(K key, Function defaultCreator, Class requiredType); + /** * Store a {@code value} for later retrieval under the supplied {@code key}. * diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java index 0c675b07dbdd..16102f41a320 100644 --- a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java @@ -15,6 +15,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.jspecify.annotations.NullMarked; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; @@ -41,6 +42,7 @@ * @see LoggerFactory * @see LogRecordListener */ +@NullMarked @Target({ ElementType.TYPE, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(TrackLogRecords.Extension.class) @@ -71,7 +73,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte } private LogRecordListener getListener(ExtensionContext context) { - return getStore(context).getOrComputeIfAbsent(LogRecordListener.class); + return getStore(context).computeIfAbsent(LogRecordListener.class); } private Store getStore(ExtensionContext context) { 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 88535ea14931..464964c9a757 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 @@ -42,7 +42,7 @@ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N public @Nullable Object get(Object key) { Preconditions.notNull(key, "key must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.get(this.namespace, key); - return accessStore(action); + return this.<@Nullable Object> accessStore(action); } @Override @@ -50,9 +50,10 @@ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier<@Nullable T> action = () -> this.valuesStore.get(this.namespace, key, requiredType); - return accessStore(action); + return this.<@Nullable T> accessStore(action); } + @SuppressWarnings("deprecation") @Override public @Nullable Object getOrComputeIfAbsent(K key, Function defaultCreator) { @@ -60,9 +61,10 @@ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator); - return accessStore(action); + return this.<@Nullable Object> accessStore(action); } + @SuppressWarnings("deprecation") @Override public @Nullable V getOrComputeIfAbsent(K key, Function defaultCreator, Class requiredType) { @@ -71,6 +73,23 @@ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier<@Nullable V> action = () -> this.valuesStore.getOrComputeIfAbsent(this.namespace, key, defaultCreator, requiredType); + return this.<@Nullable V> accessStore(action); + } + + @Override + public Object computeIfAbsent(K key, Function defaultCreator) { + Preconditions.notNull(key, "key must not be null"); + Preconditions.notNull(defaultCreator, "defaultCreator function must not be null"); + Supplier action = () -> this.valuesStore.computeIfAbsent(this.namespace, key, defaultCreator); + return accessStore(action); + } + + @Override + public V computeIfAbsent(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"); + Supplier action = () -> this.valuesStore.computeIfAbsent(this.namespace, key, defaultCreator, requiredType); return accessStore(action); } @@ -78,14 +97,14 @@ public NamespaceAwareStore(NamespacedHierarchicalStore valuesStore, N public void put(Object key, @Nullable Object value) { Preconditions.notNull(key, "key must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.put(this.namespace, key, value); - accessStore(action); + this.<@Nullable Object> accessStore(action); } @Override public @Nullable Object remove(Object key) { Preconditions.notNull(key, "key must not be null"); Supplier<@Nullable Object> action = () -> this.valuesStore.remove(this.namespace, key); - return accessStore(action); + return this.<@Nullable Object> accessStore(action); } @Override @@ -93,10 +112,10 @@ public void put(Object key, @Nullable Object value) { Preconditions.notNull(key, "key must not be null"); Preconditions.notNull(requiredType, "requiredType must not be null"); Supplier<@Nullable T> action = () -> this.valuesStore.remove(this.namespace, key, requiredType); - return accessStore(action); + return this.<@Nullable T> accessStore(action); } - private @Nullable T accessStore(Supplier<@Nullable T> action) { + private T accessStore(Supplier action) { try { return action.get(); } 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 b694f3137ca4..7c55d1f51599 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,7 +12,6 @@ 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; @@ -242,11 +241,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 = requireNonNull(extensionContext.getStore(NAMESPACE.append(elementContext)) // - .getOrComputeIfAbsent(KEY, + Path path = extensionContext.getStore(NAMESPACE.append(elementContext)) // + .computeIfAbsent(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/TimeoutExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java index 4c8a611f3eed..7d50cc928a1d 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,7 +10,6 @@ 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; @@ -172,8 +171,8 @@ private Optional readTimeoutThreadModeFromAnnotation(Optional new TimeoutConfiguration(root), TimeoutConfiguration.class)); + return root.getStore(NAMESPACE).computeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, + key -> new TimeoutConfiguration(root), TimeoutConfiguration.class); } private Invocation decorate(Invocation invocation, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java index daa4853550ef..2c066ba9e945 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java @@ -10,8 +10,6 @@ package org.junit.jupiter.engine.extension; -import static java.util.Objects.requireNonNull; - import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -50,7 +48,7 @@ Invocation create(ThreadMode threadMode, TimeoutInvocationParameters t } private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { - return requireNonNull(store.getOrComputeIfAbsent(SingleThreadExecutorResource.class)).get(); + return store.computeIfAbsent(SingleThreadExecutorResource.class).get(); } @SuppressWarnings({ "deprecation", "try" }) 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 e4a7e4e9bb0a..e909745b4f41 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 @@ -61,8 +61,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 != null && !handled) { + boolean handled = getStore(context).computeIfAbsent(EXCEPTION_WAS_HANDLED, key -> false, Boolean.class); + if (!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 c13ac33b1985..0b4c286c1e62 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,7 +11,6 @@ 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; @@ -147,8 +146,8 @@ private List getRuleAnnotatedMembers(ExtensionContext c Object testInstance = context.getRequiredTestInstance(); Namespace namespace = Namespace.create(TestRuleSupport.class, context.getRequiredTestClass()); // @formatter:off - return new ArrayList<>(requireNonNull(context.getStore(namespace) - .getOrComputeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class))); + return new ArrayList<>(context.getStore(namespace) + .computeIfAbsent("rule-annotated-members", key -> findRuleAnnotatedMembers(testInstance), List.class)); // @formatter:on } 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 0be381158c6a..eabcb067044f 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,8 +10,6 @@ package org.junit.jupiter.params; -import static java.util.Objects.requireNonNull; - import java.util.Arrays; import java.util.Optional; @@ -72,7 +70,7 @@ private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration( String key = ARGUMENT_COUNT_VALIDATION_KEY; ArgumentCountValidationMode fallback = ArgumentCountValidationMode.NONE; ExtensionContext.Store store = getStore(extensionContext); - return requireNonNull(store.getOrComputeIfAbsent(key, __ -> { + return store.computeIfAbsent(key, __ -> { Optional optionalConfigValue = extensionContext.getConfigurationParameter(key); if (optionalConfigValue.isPresent()) { String configValue = optionalConfigValue.get(); @@ -95,7 +93,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-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 72826970014a..5e9c3157ffee 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 @@ -12,6 +12,7 @@ import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; @@ -188,7 +189,12 @@ public void close() { * @return the stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if this store has already been * closed + * + * @deprecated Please use + * {@link #computeIfAbsent(Object, Object, Function)} instead. */ + @Deprecated + @API(status = DEPRECATED, since = "6.0") public @Nullable Object getOrComputeIfAbsent(N namespace, K key, Function defaultCreator) { Preconditions.notNull(defaultCreator, "defaultCreator must not be null"); @@ -204,6 +210,45 @@ public void close() { return storedValue.evaluate(); } + /** + * Return the value stored for the supplied namespace and key in this store + * or the parent store, if present and not {@code null}, or call the + * supplied function to compute it. + * + * @param namespace the namespace; never {@code null} + * @param key the key; never {@code null} + * @param defaultCreator the function called with the supplied {@code key} + * to create a new value; never {@code null} and must not return + * {@code null} + * @return the stored value; never {@code null} + * @throws NamespacedHierarchicalStoreException if this store has already been + * closed + * @since 6.0 + */ + @API(status = MAINTAINED, since = "6.0") + public Object computeIfAbsent(N namespace, K key, Function defaultCreator) { + Preconditions.notNull(defaultCreator, "defaultCreator must not be null"); + CompositeKey compositeKey = new CompositeKey<>(namespace, key); + StoredValue storedValue = getStoredValue(compositeKey); + var result = StoredValue.evaluateIfNotNull(storedValue); + if (result == null) { + StoredValue newStoredValue = this.storedValues.compute(compositeKey, (__, oldStoredValue) -> { + if (StoredValue.evaluateIfNotNull(oldStoredValue) == null) { + rejectIfClosed(); + var computedValue = Preconditions.notNull(defaultCreator.apply(key), + "defaultCreator must not return null"); + return newStoredValue(() -> { + rejectIfClosed(); + return computedValue; + }); + } + return oldStoredValue; + }); + return requireNonNull(newStoredValue.evaluate()); + } + return result; + } + /** * Get the value stored for the supplied namespace and key in this store or * the parent store, if present, or call the supplied function to compute it @@ -217,7 +262,12 @@ public void close() { * @return the stored value; may be {@code null} * @throws NamespacedHierarchicalStoreException if the stored value cannot * be cast to the required type, or if this store has already been closed + * + * @deprecated Please use + * {@link #computeIfAbsent(Object, Object, Function, Class)} instead. */ + @Deprecated + @API(status = DEPRECATED, since = "6.0") public @Nullable V getOrComputeIfAbsent(N namespace, K key, Function defaultCreator, Class requiredType) throws NamespacedHierarchicalStoreException { @@ -226,6 +276,31 @@ public void close() { return castToRequiredType(key, value, requiredType); } + /** + * Return the value stored for the supplied namespace and key in this store + * or the parent store, if present and not {@code null}, or call the + * supplied function to compute it and, finally, cast it to the supplied + * required type. + * + * @param namespace the namespace; never {@code null} + * @param key the key; never {@code null} + * @param defaultCreator the function called with the supplied {@code key} + * to create a new value; never {@code null} and must not return + * {@code null} + * @param requiredType the required type of the value; never {@code null} + * @return the stored value; never {@code null} + * @throws NamespacedHierarchicalStoreException if the stored value cannot + * be cast to the required type, or if this store has already been closed + * @since 6.0 + */ + @API(status = MAINTAINED, since = "6.0") + public V computeIfAbsent(N namespace, K key, Function defaultCreator, + Class requiredType) throws NamespacedHierarchicalStoreException { + + Object value = computeIfAbsent(namespace, key, defaultCreator); + return castNonNullToRequiredType(key, value, requiredType); + } + /** * Put the supplied value for the supplied namespace and key into this * store and return the previously associated value in this store. @@ -302,12 +377,16 @@ private StoredValue newStoredValue(Supplier<@Nullable Object> value) { return null; } - @SuppressWarnings("unchecked") private @Nullable T castToRequiredType(Object key, @Nullable Object value, Class requiredType) { Preconditions.notNull(requiredType, "requiredType must not be null"); if (value == null) { return null; } + return castNonNullToRequiredType(key, value, requiredType); + } + + @SuppressWarnings("unchecked") + private T castNonNullToRequiredType(Object key, V value, Class requiredType) { if (isAssignableTo(value, requiredType)) { if (requiredType.isPrimitive()) { return (T) requireNonNull(getWrapperType(requiredType)).cast(value); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/Heavyweight.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/Heavyweight.java index a7552d2787f2..6314bef04ef9 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/Heavyweight.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/Heavyweight.java @@ -10,7 +10,6 @@ package org.junit.jupiter.api.extension; -import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,7 +32,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context) { var engineContext = context.getRoot(); var store = engineContext.getStore(ExtensionContext.Namespace.GLOBAL); - var resource = requireNonNull(store.getOrComputeIfAbsent(ResourceValue.class)); + var resource = store.computeIfAbsent(ResourceValue.class); resource.usages.incrementAndGet(); return resource; } 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 2599244c739c..61993435a692 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 @@ -409,7 +409,7 @@ private ExtensionContext createExtensionContextForFilePublishing(Path tempDir, } @Test - @SuppressWarnings("resource") + @SuppressWarnings({ "resource", "deprecation" }) void usingStore() { var methodTestDescriptor = methodDescriptor(); var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); @@ -436,14 +436,17 @@ void usingStore() { final Object key2 = "key 2"; final var value2 = "other value"; - assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2)); - assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2, String.class)); + assertEquals(value2, childStore.computeIfAbsent(key2, key -> value2)); + assertEquals(value2, childStore.computeIfAbsent(key2, key -> "a different value", String.class)); + assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> "a different value")); + assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> "a different value", String.class)); assertEquals(value2, childStore.get(key2)); assertEquals(value2, childStore.get(key2, String.class)); final Object parentKey = "parent key"; final var parentValue = "parent value"; parentStore.put(parentKey, parentValue); + assertEquals(parentValue, childStore.computeIfAbsent(parentKey, k -> "a different value")); assertEquals(parentValue, childStore.getOrComputeIfAbsent(parentKey, k -> "a different value")); assertEquals(parentValue, childStore.get(parentKey)); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java index c39cc37aae7c..b93929f80b51 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java @@ -35,7 +35,7 @@ void concurrentAccessToDefaultStoreWithoutParentStore() { IntStream.range(1, 100).forEach(i -> { Store store = reset(); // Simulate 100 extensions interacting concurrently with the Store. - IntStream.range(1, 100).parallel().forEach(j -> store.getOrComputeIfAbsent("key", this::newValue)); + IntStream.range(1, 100).parallel().forEach(j -> store.computeIfAbsent("key", this::newValue)); assertEquals(1, count.get(), () -> "number of times newValue() was invoked in run #" + i); }); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java index 849d481212c6..d90c2ed0f88c 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -77,6 +78,7 @@ void getOrDefaultWithValueInParentStore() { assertThat(store.getOrDefault(KEY, String.class, VALUE)).isEqualTo(VALUE); } + @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithFailingCreator() { var invocations = new AtomicInteger(); @@ -92,4 +94,13 @@ void getOrComputeIfAbsentWithFailingCreator() { assertThat(invocations).hasValue(1); } + @Test + void computeIfAbsentWithFailingCreator() { + assertThrows(RuntimeException.class, () -> store.computeIfAbsent(KEY, __ -> { + throw new RuntimeException(); + })); + assertNull(store.get(KEY)); + assertDoesNotThrow(localStore::close); + } + } 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 5f3281917664..9cf00441de43 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 @@ -73,7 +73,7 @@ static class OnlyIncrementCounterOnce implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) { ExtensionContext.Store store = getRoot(context).getStore(ExtensionContext.Namespace.GLOBAL); - store.getOrComputeIfAbsent("counter", key -> Parent.counter.incrementAndGet()); + store.computeIfAbsent("counter", key -> Parent.counter.incrementAndGet()); } private ExtensionContext getRoot(ExtensionContext context) { 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 7e6e6e4f0d2a..946a0344179e 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 @@ -85,12 +85,13 @@ void shouldThrowExceptionWhenNullTimeoutInvocationParametersIsProvidedWhenCreate .hasMessage("timeout invocation parameters must not be null"); } + @SuppressWarnings("resource") @Test @DisplayName("creates timeout invocation for SAME_THREAD thread mode") void shouldCreateTimeoutInvocationForSameThreadTimeoutThreadMode() { var invocation = timeoutInvocationFactory.create(ThreadMode.SAME_THREAD, parameters); assertThat(invocation).isInstanceOf(SameThreadTimeoutInvocation.class); - verify(store).getOrComputeIfAbsent(SingleThreadExecutorResource.class); + verify(store).computeIfAbsent(SingleThreadExecutorResource.class); } @Test 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 0cc43881b122..571f321f6d84 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 @@ -78,50 +78,67 @@ void valueCanBeReplaced() { @Test void valueIsComputedIfAbsent() { assertNull(store.get(namespace, key)); - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> value)); + assertEquals(value, store.computeIfAbsent(namespace, key, __ -> value)); assertEquals(value, store.get(namespace, key)); } + @SuppressWarnings("deprecation") @Test void valueIsNotComputedIfPresentLocally() { store.put(namespace, key, value); - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); + assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.get(namespace, key)); } + @SuppressWarnings("deprecation") @Test void valueIsNotComputedIfPresentInParent() { parentStore.put(namespace, key, value); - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); + assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.get(namespace, key)); } + @SuppressWarnings("deprecation") @Test void valueIsNotComputedIfPresentInGrandParent() { grandParentStore.put(namespace, key, value); - assertEquals(value, store.getOrComputeIfAbsent(namespace, key, k -> "a different value")); + assertEquals(value, store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); + assertEquals(value, store.computeIfAbsent(namespace, key, __ -> "a different value")); assertEquals(value, store.get(namespace, key)); } + @SuppressWarnings("deprecation") @Test void nullIsAValidValueToPut() { store.put(namespace, key, null); - assertNull(store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); + assertNull(store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); assertNull(store.get(namespace, key)); + + assertEquals("a different value", store.computeIfAbsent(namespace, key, __ -> "a different value")); + assertEquals("a different value", store.get(namespace, key)); } + @SuppressWarnings("deprecation") @Test void keysCanBeRemoved() { store.put(namespace, key, value); assertEquals(value, store.remove(namespace, key)); + assertNull(store.get(namespace, key)); + + assertEquals("a different value", store.getOrComputeIfAbsent(namespace, key, __ -> "a different value")); + assertEquals("a different value", store.remove(namespace, key)); + assertNull(store.get(namespace, key)); + assertEquals("another different value", + store.computeIfAbsent(namespace, key, __ -> "another different value")); + assertEquals("another different value", store.remove(namespace, key)); assertNull(store.get(namespace, key)); - assertEquals("a different value", - store.getOrComputeIfAbsent(namespace, key, innerKey -> "a different value")); } @Test @@ -144,7 +161,7 @@ void valueIsComputedIfAbsentInDifferentNamespace() { String namespace1 = "ns1"; String namespace2 = "ns2"; - assertEquals(value, store.getOrComputeIfAbsent(namespace1, key, innerKey -> value)); + assertEquals(value, store.computeIfAbsent(namespace1, key, __ -> value)); assertEquals(value, store.get(namespace1, key)); assertNull(store.get(namespace2, key)); @@ -213,6 +230,7 @@ void getNullValueWithTypeSafety() { assertNull(requiredTypeValue); } + @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { String key = "pi"; @@ -231,6 +249,25 @@ void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { exception.getMessage()); } + @Test + void computeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { + String key = "pi"; + Float value = 3.14f; + + // Store a Float... + store.put(namespace, key, value); + + // But declare that our function creates a String... + Function defaultCreator = k -> "enigma"; + + Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, + () -> store.computeIfAbsent(namespace, key, defaultCreator, String.class)); + assertEquals( + "Object stored under key [pi] is not of required type [java.lang.String], but was [java.lang.Float]: 3.14", + exception.getMessage()); + } + + @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithTypeSafety() { Integer key = 42; @@ -241,7 +278,17 @@ void getOrComputeIfAbsentWithTypeSafety() { assertEquals(value, computedValue); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @Test + void computeIfAbsentWithTypeSafety() { + Integer key = 42; + String value = "enigma"; + + // The fact that we can declare this as a String suffices for testing the required type. + String computedValue = store.computeIfAbsent(namespace, key, __ -> value, String.class); + assertEquals(value, computedValue); + } + + @SuppressWarnings({ "DataFlowIssue", "NullAway", "deprecation" }) @Test void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; @@ -254,6 +301,19 @@ void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { assertEquals(value, computedInteger.intValue()); } + @Test + void computeIfAbsentWithTypeSafetyAndPrimitiveValueType() { + String key = "enigma"; + int value = 42; + + // The fact that we can declare this as an int/Integer suffices for testing the required type. + int computedInt = store.computeIfAbsent(namespace, key, k -> value, int.class); + Integer computedInteger = store.computeIfAbsent(namespace, key, k -> value, Integer.class); + assertEquals(value, computedInt); + assertEquals(value, computedInteger.intValue()); + } + + @SuppressWarnings("deprecation") @Test void getOrComputeIfAbsentWithExceptionThrowingCreatorFunction() { var e = assertThrows(RuntimeException.class, () -> store.getOrComputeIfAbsent(namespace, key, __ -> { @@ -263,6 +323,15 @@ void getOrComputeIfAbsentWithExceptionThrowingCreatorFunction() { assertSame(e, assertThrows(RuntimeException.class, () -> store.remove(namespace, key))); } + @Test + void computeIfAbsentWithExceptionThrowingCreatorFunction() { + assertThrows(RuntimeException.class, () -> store.computeIfAbsent(namespace, key, __ -> { + throw new RuntimeException("boom"); + })); + assertNull(store.get(namespace, key)); + assertNull(store.remove(namespace, key)); + } + @Test void removeWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Integer key = 42; @@ -316,6 +385,7 @@ void removeNullValueWithTypeSafety() { assertNull(store.get(namespace, key)); } + @SuppressWarnings("deprecation") @Test void simulateRaceConditionInGetOrComputeIfAbsent() throws Exception { int threads = 10; @@ -331,6 +401,21 @@ void simulateRaceConditionInGetOrComputeIfAbsent() throws Exception { assertEquals(1, counter.get()); assertThat(values).hasSize(threads).containsOnly(1); } + + @Test + void simulateRaceConditionInComputeIfAbsent() throws Exception { + int threads = 10; + AtomicInteger counter = new AtomicInteger(); + List values; + + try (var localStore = new NamespacedHierarchicalStore<>(null)) { + values = executeConcurrently(threads, // + () -> requireNonNull(localStore.computeIfAbsent(namespace, key, it -> counter.incrementAndGet()))); + } + + assertEquals(1, counter.get()); + assertThat(values).hasSize(threads).containsOnly(1); + } } @Nested @@ -433,9 +518,13 @@ void doesNotCallCloseActionForNullValues() { verifyNoInteractions(closeAction); } + @SuppressWarnings("deprecation") @Test void doesNotCallCloseActionForValuesThatThrowExceptionsDuringCleanup() throws Throwable { store.put(namespace, "key1", "value1"); + assertThrows(RuntimeException.class, () -> store.computeIfAbsent(namespace, "key2", __ -> { + throw new RuntimeException("boom"); + })); assertThrows(RuntimeException.class, () -> store.getOrComputeIfAbsent(namespace, "key2", __ -> { throw new RuntimeException("boom"); })); @@ -447,10 +536,10 @@ void doesNotCallCloseActionForValuesThatThrowExceptionsDuringCleanup() throws Th var inOrder = inOrder(closeAction); inOrder.verify(closeAction).close(namespace, "key3", "value3"); inOrder.verify(closeAction).close(namespace, "key1", "value1"); - - verifyNoMoreInteractions(closeAction); + inOrder.verifyNoMoreInteractions(); } + @SuppressWarnings("deprecation") @Test void abortsCloseIfAnyStoredValueThrowsAnUnrecoverableExceptionDuringCleanup() throws Throwable { store.put(namespace, "key1", "value1"); @@ -527,6 +616,7 @@ void closeIsIdempotent() throws Throwable { /** * @see #3944 */ + @SuppressWarnings("deprecation") @Test void acceptsQueryAfterClose() { store.put(namespace, key, value); @@ -535,10 +625,13 @@ void acceptsQueryAfterClose() { assertThat(store.get(namespace, key)).isEqualTo(value); assertThat(store.get(namespace, key, String.class)).isEqualTo(value); - assertThat(store.getOrComputeIfAbsent(namespace, key, k -> "new")).isEqualTo(value); - assertThat(store.getOrComputeIfAbsent(namespace, key, k -> "new", String.class)).isEqualTo(value); + assertThat(store.getOrComputeIfAbsent(namespace, key, __ -> "new")).isEqualTo(value); + assertThat(store.getOrComputeIfAbsent(namespace, key, __ -> "new", String.class)).isEqualTo(value); + assertThat(store.computeIfAbsent(namespace, key, __ -> "new")).isEqualTo(value); + assertThat(store.computeIfAbsent(namespace, key, __ -> "new", String.class)).isEqualTo(value); } + @SuppressWarnings("deprecation") @Test void rejectsModificationAfterClose() { store.close(); @@ -548,11 +641,16 @@ void rejectsModificationAfterClose() { assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key)); assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key, int.class)); - // Since key does not exist, an invocation of getOrComputeIfAbsent(...) will attempt to compute a new value. + // Since key does not exist, an invocation of getOrComputeIfAbsent(...) or computeIfAbsent(...) will attempt + // to compute a new value. + assertThrows(NamespacedHierarchicalStoreException.class, + () -> store.getOrComputeIfAbsent(namespace, key, __ -> "new")); + assertThrows(NamespacedHierarchicalStoreException.class, + () -> store.getOrComputeIfAbsent(namespace, key, __ -> "new", String.class)); assertThrows(NamespacedHierarchicalStoreException.class, - () -> store.getOrComputeIfAbsent(namespace, key, k -> "new")); + () -> store.computeIfAbsent(namespace, key, __ -> "new")); assertThrows(NamespacedHierarchicalStoreException.class, - () -> store.getOrComputeIfAbsent(namespace, key, k -> "new", String.class)); + () -> store.computeIfAbsent(namespace, key, __ -> "new", String.class)); } private void assertNotClosed() { diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java index cb3eb217fa75..50d1f3aac0ad 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java @@ -32,7 +32,7 @@ public static class ScopeProvider implements ManagedResource.Scoped.Provider { @Override public ManagedResource.Scope determineScope(ExtensionContext extensionContext) { var store = extensionContext.getRoot().getStore(NAMESPACE); - var fileSystemType = store.getOrComputeIfAbsent("tempFileSystemType", key -> { + var fileSystemType = store.computeIfAbsent("tempFileSystemType", key -> { var type = getFileSystemType(Path.of(System.getProperty("java.io.tmpdir"))); extensionContext.getRoot().publishReportEntry("tempFileSystemType", type); return type; diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java index ce872f8f0f95..3324fce2714b 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java @@ -95,7 +95,7 @@ private Resource getOrCreateResource(ExtensionContext extensionContext, C case PER_CONTEXT -> extensionContext; }; return storingContext.getStore(Namespace.GLOBAL) // - .getOrComputeIfAbsent(type, Resource::new, Resource.class); + .computeIfAbsent(type, Resource::new, Resource.class); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java index 9621414e9d4c..c9d84b2c7767 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java @@ -40,7 +40,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - var outputDir = extensionContext.getStore(NAMESPACE).getOrComputeIfAbsent("outputDir", __ -> { + var outputDir = extensionContext.getStore(NAMESPACE).computeIfAbsent("outputDir", __ -> { try { return new OutputDir(Files.createTempDirectory("output")); } From 6019c7021573c8b1a3798c52476d31d81a746f86 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:09:04 +0000 Subject: [PATCH 093/162] Update plugin org.graalvm.buildtools.native to v0.11.0 --- .../projects/graalvm-starter/settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts index 6a59f78e05fd..78c78257c11a 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts @@ -1,7 +1,7 @@ pluginManagement { plugins { // TODO Remove custom config in build.gradle.kts when upgrading - id("org.graalvm.buildtools.native") version "0.11.0-SNAPSHOT" + id("org.graalvm.buildtools.native") version "0.11.0" } repositories { mavenCentral() From 7aad49f5f17408e9760c585c3b5f3905d726e9df Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 15:17:54 +0200 Subject: [PATCH 094/162] Simplify project used in `GraalVmStarterTests` --- .../projects/graalvm-starter/build.gradle.kts | 21 ------------------- .../graalvm-starter/settings.gradle.kts | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts index da1548337dea..c95ee2ad9395 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts @@ -28,27 +28,6 @@ tasks.test { } val initializeAtBuildTime = mapOf( - // These will be part of the next version of native-build-tools - // see https://github.com/graalvm/native-build-tools/pull/693 - "5.13" to listOf( - "org.junit.jupiter.api.DisplayNameGenerator\$IndicativeSentences", - "org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor\$ClassInfo", - "org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor\$LifecycleMethods", - "org.junit.jupiter.engine.descriptor.ClassTemplateInvocationTestDescriptor", - "org.junit.jupiter.engine.descriptor.ClassTemplateTestDescriptor", - "org.junit.jupiter.engine.descriptor.DynamicDescendantFilter\$Mode", - "org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector\$1", - "org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor\$MethodInfo", - "org.junit.jupiter.engine.discovery.ClassSelectorResolver\$DummyClassTemplateInvocationContext", - "org.junit.platform.engine.support.store.NamespacedHierarchicalStore\$EvaluatedValue", - "org.junit.platform.launcher.core.DiscoveryIssueNotifier", - "org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider", - "org.junit.platform.launcher.core.LauncherDiscoveryResult\$EngineResultInfo", - "org.junit.platform.launcher.core.LauncherPhase", - "org.junit.platform.suite.engine.DiscoverySelectorResolver", - "org.junit.platform.suite.engine.SuiteTestDescriptor\$DiscoveryIssueForwardingListener", - "org.junit.platform.suite.engine.SuiteTestDescriptor\$LifecycleMethods", - ), // These need to be added to native-build-tools "6.0" to listOf( "org.junit.platform.commons.util.KotlinReflectionUtils", diff --git a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts index 78c78257c11a..f87a98392e4a 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - // TODO Remove custom config in build.gradle.kts when upgrading + // TODO Check if classes can be removed from `initializeAtBuildTime` in build.gradle.kts when upgrading id("org.graalvm.buildtools.native") version "0.11.0" } repositories { From 336357c7d6c2b9e6c220302e18665575b3e59caa Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 28 May 2025 19:38:14 +0200 Subject: [PATCH 095/162] Upgradle to 9.0.0-rc-3 --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37256 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 delta 35551 zcmYJZV|bna)5V*{Y~1X)L1WvtZQHhXxMQoaZ98df+je97^#6O#xz79h)jhM;%=fb< zejogP5xmysJ1}Y-zK;P#^eNya^!*RyrWsaa*o?`cG4E0x(uI5*J=Ql{I8pVHbrf*&ViJbv&0$Zx^9HzKJYQ+2@eUCip7Q~vv%wZxh=X(hybkQ-d%4h08A3r-BgR1yDQOhGU!yc)KY_R) z<~z-KN~9P>0@{5up2;>ZO7$o~VmdL?8yt&VFrbN!Ax~@SD^gB(*;lok#cYX1yF0ri zTfoNS4~q_qcA&~muAcevb&3QXO?~0wIJt9T@@k%iwWyg|@`P{EtB0FDW2TTpJ449e zuN$b!Af;6128-YK{g=RgMOrWWfwmiBb%I9~ClxAv$Tv$EFuBIYWT39uPZWMY_)u>-6QS>Dpp%(#NEFIeU zjJN#v$j{|sq!va#kM7Uh3#%b(XnIqbX?K%PlWA%C!0rz)hR9!_CvWd*YWqemcDG<_ ztH|`aB23nP=k&Rwy!(xW{j|Wn?pi2hNM1G%1t1en-wK?TTrRDhBR7g@m1Q#C7R_i_ zL3gbJo7pkkx%%3RHtl+`z|2k&Q(IqCA$2glZe)H(AF@Q`UUFJnn$##p$J+Wg29V06 z^$W;@!nT*;@Fm6WWuq~~ZbeD|5ihjEEcv%uhGHE&8e;#tPwF|FJFRb1H*J)HAb-%_ zATZ3|un`ABE3ffkn8#v4L?T+D&Ath57i3+NL7H6VrjcSx00}9XLCoNTea8^xLS$ul zj~YlyyKT+NZn9!<(nGF`y+z)ulWL?2y{qJxmB*f{ug(}O0}n4IaigLNKcqBbBr*t= zAbGz_({CW|vYA*MC0CMUm#7EfqwiX&)Q#eM9U657>_Z_=xQ_KLM zO%6h`rx~)x-7(vp@br}&k(TFMBXDg~(68W~7Id{DO7>I%!1Is@@Z$NA0*S#kM~}+M zO;#+U>;QsYyR6@9itLyZXt?aMAe&1UyFw@2JH?lLl_gE+<6YSM)@Ls;5 zX&SY^f>-?i>qi@tYFRsQFtCPi5dY~o7hMQ=A%`xA!7Ch4v_2OI`%GK?^Fs@VApw2} zQc^|&han&EY+T$iZ))h?oVJ-iFcS2P_&EdlYjyzUIxot79StR&<&wfumAu}Bs9%YpbNZ+1Q6_U5E>>Jo(Gcc?vo73mT|MU zjZUVk4qN7C;+OIaIiiV369ED#h6Bf;tb$G|3w$vB9@Xu`$R4ZvbCmXCj*}^O+=%@F z?=UU%P|G2nihG9%jS$(?h*>v|@=Mlj^g-^oXqx>TK_|sk=2c$Oy!7?DbCN)O^j5Ja zz{rC@_R^7N3(lv$2dGRhkafdoB)-0To|uCK*;$MQWvw&`~J&*b;AnbCAg8}xm^Q^Ypo+fh_OqPzc* zWPK%OH*$E-|C-La5++UiU(+>1{?~KIM86Uve~<&^=M6CY^aS9WD6nq)uraZ1sL^LQ zf3yG5CeC$~Vv=FGYEP}28=rH_Wqf6pxo_YXK*uDxxt$y!H09AXhZG#cTCTkC-a5{_ z%N+N9-9Ij&2NQD)+FiUmcCVLTBwkJp)>R@`@l}*9Yd2O!N_+zuTc;?ak-CRawvt;k z^zi~^YhZmxD>SpY>PBSc3m2?38$48*!Epy=%tQ!zr8U^!w1IVI>7>_GI=Fd7wc{Y# zVCxmr1UiIe5`EI?@3BbcO$i!mIZXkKBc3HkXM5>}@Sv#ulzG$CRGIiCSrXn0jUO%2 z%qFL7?!3E?^5LSxzZ%b9UbO1!=<`B$bqax(RaPih2k`E=37ylvM0v@1i!}hfFH2}w zvN4&MnPa5&YkDRf!YI&JbZMmYxkFo?CzP#){V*K`yvg4bB12^1P-ArAWn@og8pJ7{ zy>T8}r;g02H$f}sj9NjTvesSpv8>v?J?qC)J#KIT40LBAhIPXy_OX~v?1ArOJy zS?%=pXOb4ddE_iQcSy{>LEg!ldXtnK!TlE;VI+vU8O^`&j4kL8atsZ4XSD~#g`Oy7 zGeqF!ev<8TyfzmZbk;|X0~V2gb_O) z_@8OloSoSzC5RX0@CzBks;Dq5iQ0hyOD%F5+l^6>C-0{ET4N;K8!XeeGZ%@J-Dk7enSJ zxiQ``wpU9n8nmzC5P}3s(FoeBXGkf+k{S-V&gy@9;e{_NBv0L=|T!{Qb zcmbg?KO`F&&H99L0;=@mYUbvJw@i%PP!!X7-kRqpAVkrW}Z(P}X7Kut#HlOn0( z9;4KaiG_OrL*-N#+++{f|Fi@p@qK^}0t`$y5e3H*cP^%2H{CvQuOlDf63e=PD_TZ*Er2A}3kqg z;SOi^KKTtFvm~xW?E-yT+S`VA&i2P9?e^Ep;W8N8{ud%WA#Z!l#p6tFI^TdS?E--m zatLuAurYb^6m)i$f<38)L*6!tRLzz7JyexEo#5zHSdQ;Jcr8?=e>Yx%4t=t`t(49O z(Qdt&vg?Iuu4z5uQP{KpX8?1h82cjLX5+DUWdfiQhQMoZTU_7Ogs() z$Y5@4-O?}G&H*$|%Z)z1Qf_vwu{LA8sm4|TOxMcfxlpwYT~GbXSf$v&PVWDfP*~Bf zBjj&*S2=|F_lS8UgH~Ar&gHZS$3gla3sqMKU1XLSYuBq zC|pj}*|05*nI|HNO3`8=>8mw3s@OgK3kzgS-~- zA4}J0_nB-EjHu~K>{aJWO{7RJ@p(q(?Zof=u+?*Q71nl9MNkhA>8$SNiaF>*kfe9-5ZZw9$5s?X_wRv+66j-AiQFTAX9C6boKn)z=SGf_R zs~dTH*P?QqE2LOcv3qjg9_gq)g*=!pQR~e%#vNv(;L4<1^$%3%xsZbL>dFQTTTB7L zYJX{FIgt1AxOn_SE#tU=ueLfv1x8GC!^TY4aWf6AO2AdhCKRXWJ54saLUsu}9e?UIF{9wu)__c$BjVfHHJV;A zhYVV#cIZ5%7iJAy*D|&hb93@El0wF)$Nce4RlU%4s}FbBKDa0lNj0b?i9*!eliscz zodbJd(Id6B#d8UVh-(`Q;ednhCz)^jlD5p2xStUJkK;xI@Xh<>1S@qFad|%OkqbW8 znVl68ZQ*?W*2Pk+^~|laLAs~x#?dbF3&$%-@9lZgq1rG%{)bP1H0d|CU}c!^Dzb*B zmNfDgX?o{Rf5?QfzwnSI21 zkYHzU9R=B?O7mO6gH7q(FltF9hECeLF~*f%HF(3jjpO8j1^k%VLT4%(f70AKl7vuV zemQmc>s02~G!f*z)z$29iJA93EdehD1_jCx^f<^ub{-T7yt-^~5_>@qTbGwMJx7lP6}LNr(_prpAFt zWd~4xIkP1FMzdYf%d;^c2==XPj+g~5Pf#g-& zLgR>80`CNs$QgV}R+hyjnn!Tn^!A|Gzkt^;Sk(-{c6Ie$(>6cGjhBwRj57B;6MV6U zyBD+W@8+8^8|o~h6Ky`hPWl!mg*{7|`$dUGT&_U?A+-lycI%k=(ck3<-YA_u(K+?` z6GhRf$0LMU#JLrFB1u0M2>KU(LKmH?S;g@*4R76n57qV%1 zSR+cm4zfql_dUk+8De}Do~3@VQP8`qqx@vav-B0=e}nJJ|1xs}8VtkQ-oc40NO4+*oMypQV@`FbPBrinn*))GcdlkzS`|6!Qz~ z=|xUIk$K-iz81%pmo}fF5wuA3zU1}IKF-W`zMR(I27;CL8a&tbeC6NBSvxw*k2E)z zr{Px>re&`;;S;Q7v*^^&j$9##Ukl6(>kT!v`N_ zo;v(qg(sg1qnFN$u!z%@WY=leHXC-yQ_d%dU3&h8Ab(Q!4#hKMUu)`vJOzd+1+D~d z1GFL1{z4#D1;d6N!6+}RhlFAD^OKEb=o9wk89C~RJ#*B#{M|a$oWi^ULxBqZwPtYvb9qofWYm z-n-zqIruA~1uuY#RX?v|oB?YR{DRCPM+~$?ob@BF53nk;>w1POhuK5?hCRzHe&qwM zMXV+PsT6T%4z2MHI8V07A{{rfr4j?zBOSz8P3yxlfoavEL2|fI&TorKhD?!WDIw8t z1oMR*Ex3k3vm{4R@^X#CjyxQWdqw(RqYe1?a?AdEt)%|%wIY}}PD%z;v6i1#0Qh~! zO^SBJX8)#`7iec=sslMBIznn8;Xorm`W%w!8meT$?X*TTFoJx;{w#=;DuNF5=O24^ zgE&m7l$G<&e)7zDa@u-)$|39li!uz@y&E0XdM!vle(iREKZ`2ADwR~FUxO(gy zaI5`|_# z0pHNAj-FHF0G+}T$qxU#SCB|GLd_;1Ae6I)axC>LhcSk&!ID55;6I*#p`(v?jrA51j3d%qd;tN)@r8pvbNX_tH_#~N z5tdENu+KVm=kWn;p}ypq)7i}U^BLwI=oNA`1bm-#febi8rK0G<49$NbP#c5ue&Pu7 z3U!x7=M5eWdkTg~)yy$~Vphfo_zx%}xy7tD@1{-JKC=bGXHb2BK| zo-7D9UqX>ZaO6L)B%_lnHJ?-+HR)fpaLFtR?Ren&uh_ZVli996H3AA|AMSWCx z(%F_pOiH)=nDY;2Bnmey!G4Ggjhn&>*HJ`&5JI%GG$*g%HVdXiP=tA+jsfi%t65SQ zq?8j@cE+Bp9a)o|x@%LWY-}k@^@y9xbBTQ@;wq`faHl|ph<=HXT*CvgeQIn9fN?2% zaEpawYPn71V2!CJwB!yHSs!4SG)S#!H4Q&Pi<3cJFx~KaN@k1S5p^P%5s52rhuHTF zak86IyZ%nd?z;0=;0KE<{D*@T%0noMMfj_;lmuARJFca#WQQIk9MRp(lG+~PWB@`V z+4RgO(x)k=C=3^Un!H2>C|fGO=^QV%dxpB7r^@yI{)&PCy-a8-zEqw7u*N0&MhT66 zEMb$K|H3WCKF!$lf`A7eMEnftQ zO|p_WO>P0~mBVF3!B32v0Sid^A&1v~MkGk1t%ND6K=chQUkS3bjKks1iySv-xud>I z@s|o;A+Q&&EYuH-Fa!|#(@Xey=h)N!$kXid^6L}A|9d6Fv$O9KHF|-vj)W!UleoL%#wE7t;Gp<9x6 zlP(A-RpHA9!+c%*&DDaTw7I)w8i(Oxdr~Jc)^YfG{30!>_gJmt$q4t0wN{w4p`(IB zE9;H8xVP*6{uue&OfU8s`uRl2_Ln zkaBW*#cY7M3ei&`b2Ann*n6F<+kn|pSeiChX8Tq>&TAc-^w3$NL zVYFD*2}8aZH2~m2)l9-}UWDObZ~L+RygAsbUt1|x4!X#at|TrttAK*=jZFZsSUB4) zRU%4i@vTj&!83g04C;0fVZ!elG=`UbQfnxws6c^Jj8ERma2K-1GpNYyuvMWm*e_<4 zFZ*8cHFyuU`W+4*NJb}|{D|QjO3g??e)Hd^q|@S#`u*Pk6aGKM8%ZMoRQx|(lM_ip zP*Os9o#jz~mrOQ=!lVEn_$E>$h59q_|I>9$XNCl9GV(4x2hqbHnEL{%AtHr1;=zOu zv!m$k6=vYqhbN>z(sSR=<>O%O>-PF~E1t-i}gF}=)MYQ*u}$xl{BrHy={Y@&GH zY^eOuJu2KnU|P@SAyt3zwtQgH6T~S?epQugU7ciG^Mg|lw?YKCW-QG4LB3p}Sfdg- z27dlz>5oBeYyKrI!6@OcCmIIm#qu2StheP>>R4nu?I zJX#965ONPvine}|{x#GkJ(VXCU&jpZc#1RD;cL%H2Oy@ntD)gkdXIEdy-(nFwKoA& zKEB<=tRiF#E-caJpS+XqIMj!Hk2aSQ6*il?8sOPCYI4A3=o};dsIC0( zl;d>jysNuE)hP4MbRhdd+hu^uS@@}u%YeU6Dti4f~w4u_y-OdV|-qWIxu4wxJi&zm+Z`*e%3g|;(`+{7XM!8 zI>6wx(N55j-A424OTn?gL$aU6?r{&=juA0SF-}bGgQQs&@?vkfyrVB7^;R1P{`ct5 zSYq8F_%0IAw_iq0m+B!tqZQeI@T!PqYd8Zc+YxT-&$81~?80r}3jq-Kw6m5GQFz^8bHe!Tw8p6A5v?|G&v4YC<_OFj`et8(kd3Zy1t&pix4_hUScI5e=LO z3Ip}sB1(fY?x&!wh;-;Ck><+Zp-m*ID!u3X_UZj1y~m;TX06SdGR*2ICyy+)El$_nQ&f5ED0iBF!_aW8}C03bB zAa-+d`AYlG4icGOUBO7x%i_lRnWIgu!D!?Or+Lh*8!JlH-Nhs#---JNS8Lu9xbyp( zi=3)7GVBc|dDnRrjbHs}eT1<4s=@^xP0O3eFoqkj=Gur3C;jZ*^LU-!G zr&*jKRJ`b)QNDABj-aK1i%9+LYQB-*YE`!mR=!E;-HA5HyAYuMj+w$8Vd$bQI+a`% zBNviFF7}{{4kf%^Ngs?MxJFSRickS!an?y$;TN1* znzYVm@a+xh<%(Q71yt=WF6&CM1l2?@r}UrI}22@E%dS9)9y=L2PL;JFofWk(y`JSpqLDX z8`jpc2kNx@96s@MrU8K6%hFvm5_0s8<170FhOtjByI{uf3{v9os)~n=NJAO_0g1Zh zVABd%%;0+$Tz4F}mq9k)JX0wBgj|4%_~q(CJ#F}89%9Yf=qMtvk%2?vD}Q|%b3zGl zuRRj}rUz--cqt4AEj&XE(cdfb_LxcXJCxE9Q>oZ0+TeqGW4`5SteqNH)ie2OE?)C> zGmdGj{J<(1dsjwkSByP8Qi#9nr;(Di{|6(bzlmkanv_1s{ln8=tZ?++&C+cm2V&O5 z5qnmhLjzB9DDMC$&+!g%fZpeQzOuivZ;UL0o8mz8{0y~V;R6+pC9%{iKNB#edaaM4 z0O6a;t(SwW!?E^?-!0{acYzJtJ+Q0c07uB*-=x8?))4$@F7Xvs$dausbVP~M16O-& z|LGHA!}v^{v?uZN2aQN*0yRKy=)_+8Z=3GlecZ=zBgaY!W2hW@i#*L zG3Vt0S*qV2a*$1-J?jyVvkLZtBa%WSA@W;JSQ831TF zHx5%;G(+9{m^RQELa{DUM!OL-xQAyL#DXlSTQTaf>*qxgf3xC_th+-(&IDA-Fu7b#_o*gJKFMg|~NnuNAh zv~7Qb&ksZTx6lS{m$%8YIk%vQr=fd@?-X;5+UIr21qNe-#=m~Wlewu4Wv=M7{m}Lfct-P!JypG))+PpVMO!;aoe!Ey2G4tIji181H9N%Z5*!>P0%&9)kd z^Hs!}Q*DKeliE$PiF>8T%{C7p38Rv)Q*BDz;;HcPC)3LCvY;AN)^sPbtSn?`2W5v9 zbOb1ejHL1uDHlqHfnn|nmmhW*d6qyWiAXM7L>n4^?n0tzyX65Bw9YCtV$MG$u5fnSPCIzPKdidn!{cKt=OInFY<O_65e(4m6jj>(r+GP9S`_g_21ajkkIIA~ZBwyHSPy2z}M zn-v^#)4X19DfwQOA7nVAW-Zhlih~Yps=Z|=$bhoF%G&98-|oR~g+Won(9v#}up5t z5i8fYQVE~dd_2`s{W<2wHGTIVT98YnqTQKJWg6`Rq!VeYU)UsVI>~b$L;jv3yKkg? ztY0kN-oAMgldw=*G!p_#cg_;zApXv~vrQG@4jOG4gih|S%_sE2zmM`D`h**C=B_#! z23%l_d`385|8cZPLsDtzQaCJP~T z9PjnVf7sCGNU)XXpRw%z3uf^XYq`0BlT!TxD4$E^Wlf)rXN$t$^NkQylaxeJdLu(3 z0(Trc(u%FwC0AwPi5~@h5Ri!}p27H%IA}fYm?oYYwkQ5RO%G%FLsTMkMh&x1lJ`(A z`p=Enzmy+ey--Pm)<$&9E#pj38SO{oTn3Ev+XWsZk#yoYdKMFhX0!RDf<(RpA$Uhm z2ng91dQrV?@2-4n7(j5#se(a7MRjuFm2$>r;wJdhM%`_|)@?*$oR?`+*nlxxH4V|! zwYWcOX8R1yOiUP51^w2R_@Y>v2_r04&U)q?nydYlf6jvNMrTG?zH@KFD7A%p2E4?x zKyd~{KdR6>+4ebG9~x_Syayv0lyEJ+r2S+3$JG(=Kd7%2Fg4zWuMFD)F;yxkj19jz zm%>fxU3Xb9TtCM`S)tpmg-hZrvx;RQkRR4oCsUN2y|7}cAgi*_+(>?H<~EQFT}Eo(2^iFDwC9AkZet# z5#q&Qmt?l+QFxYOt6#!xe7#%SG`XV;8*A;Vz`aJ#Yl%X9^HsR^sZ4YeN&bkonEJ*P6MVr|jJh2uo4C4RRoavA zop>D5G0n?cjd0Eq!X>n=8c|MhZ%a!)4Gz)n`cJxU?l5C;mDuGYOX@iWsgO8D9JF@2 z!hD_J@aFY8h}+A;)lYm9L+n$qEIoTc?1;DNB(a z8>2L)>6rAXg-qsq?TKuWs8Q}vEjPw1XyR4qY?8`HMrCKW!+i?^f6$K^!Gi{oMuFB{ z3sLRPcwGu}dw&7)N1aF%m$ezL5SztBv-fTH(|6vo{1|3W-SI*%5-ILg5L4aQ4$!7U zFWMOO_BkIBCS2lSZC~L2ZkEj76ma41B_qwF?sjU z|04y*)sb?(||E&lT#$>pD6CWnNH!Fw((H;ycad1NT?yqe5d^?Y^y0yDtE z1@Eb@=|QUL6Dg-$Rcs|JcWlKk=gF`nLC9LC7#AOCB@v!OPeeZ@VI^XHFg@!30M@Z& zH}`Aem^%G99V1y?$1UANu5|4Oe(cWypx;HrAm~Pm*U&g^mBo$^c&3efTJQYK0nru& zpE`jk7Qkugl9NO>Qir$>7P%}u?1(1X5lzcIM&-KE#iXjeSgf%mz3Fq1anZ<|vZbjM zoq({xgU*zx4JmaG>2YBMSR{BPFm&x~Pr|^^`MfgdSK}J&%#Rb(Tc$kpMDJHEE2@d2 zKSM{yYa+*vvLgdCy-V1U`hULZA+V^by46N3F{#agLYz4` zUG#=hr0u_hMPfT8T*J+se_{RTmzSh|(WqxzM; zSfBs7)+8`1DDJe-GCROPxx#p;_w=>Pl|mSC{~L-(!^0-=PBN&37@ZApI0@R-6gw)KsEY5($Mcyky-?|xirLHS zW9XR{=TXubo?YMKgF6Qrf($ifB(Mq*<UH0{XTb81#ye;beWBetn$eD6e+qycgClN!mf#Dg z%>N&YA5v93>ibvOg8wQjE-D6O9g4$}+-Y~HC8<&WPF#;R@QqaN-*M2Me{19L#REq} zLq%F0=g(Ur9|$bEpN=~a&lDo--@c)xTDrQbx=v0!5$gAR;~3HnK~7Djhq;eeFHOJ56K3EIa+d&YO$3sACzE^b)+nbAM_Ua^30JqT$TiegvS$OGq^n2tqs%Ie17$;kFs;gc zPESj9ydud2g$?iG9m)8BY8uw=dQCF}(PU_iCIVW{_?VYX(_c$DSzoJ+QRC~Gu6opX zdLa`ulUY2;(_Z5CUd*>hHecxHQV9m?M3j{9tQ3D+zRcJ9Z2z*?g+hcpl-w4d7z_7N z>ZJB`lBv#(d5X8=mr0!s&0=l5LssT$ue`Eup}(dt6n1pnVTTf8s6#ddnp~s*&l}HL z@A+c>6^G!z;_!+q02S@$)i6FU=N76QrKNBwRN@v3Xy9ap5rQiNkkmj)XiH^+qVZ&P zxNk#_=PSEwa`7mg*F*i;9)`&4``PhJO15)D=!wl=EEhTu1sPzIDL(%s*m2B#?9&Z= zf4HjwOS$IkcSk0uRKH5IwX=oWW=oZ=FrLa#n>p_wh~4-Dq<;X{R?vZ$zgCzrOAY;1 zL0wtJa2ays6zZM#oBd6$Z20Y$`k{q7Rpio~XW!V_`CZn^9R-S;r)7LfpSzAe?CI-w zQ5Yf6fauLx-)e}}=nsgyPgp?E7NU`5xb;8aY8Buz7IV-{KDM6l^d^*21HImjY{k3`_gibq~f&{L87;FV|hGZfi1^G{_&M|VK1UbXzE^}wXWXvHo@5ZjI(%@UW2 zNVlHFJC-tYoVeidFa;ByulY32ktG+^p7N^s?c1#ab3NtdKwpc9Eq`w^ z*CYoZNaB|IN|2UvK@((bk8)l|*v5M^s4IQH*fryjZRiDrWA9*EkyGl#I1G$|FDE_i zgH1ug8)VFKX&qrm%XAEK^0n3Hn)9{@xrFcUh1QLx-`CR~$)F+V?N@gzv zmuVq-oA4n}1`4|GlBvK0QGm<*(AMYg&zlEw|2E?0$Xx5apBLGKQ=O!~&H)r-dHlxp zedq0_{0#2zDM+4We*9aoQD6Yiti4@qch$SmuOs$k=dPW6kFEm8o+bO`@5Gov2BgZ^ z>Oa+`F*~9#?BN%$e~0<^ZvGs))DbAz;;?e(~n8zm1*Xb`ObOfp6K&Rm}pt}`QLsK%fjbE z^>4p8_`mb*Z_>iRb)|U)4Bb#|X;^jC0bCq~c_Hm@y-uhB#CrY#-wgj=@8Hb|<4PoY zB?Ly15bnV|N5!Nln&IWR48=Na?Cv!VVvh#jwpXnt{oo|kIrlK~R<7_ya zfT<$dX82?Phi!HT$DCLZWiPAG!)a8N$fq&rg!ea4`L5E`Y_gBVu&st<*6)X~weIV6 zERyq-kgLiSa;ac*^+Zvcno7k;gvGTyA~#&!@zSXBi*1=)PV?G&+CPzqkI2qyN%amx zqyuxVjx4~v91TZ7?b2}tRCKwE%P#SGZ#^pY@i%X?_mNnu6I zx|-<)3UwM0D4#ghZ~0u<3wttP?AT}T0g}Vch{Hw}ytK`&SuwQU-O8ncSnZe=t%Eaq z*;!*5YEmY3vVOd6DC+6B&7k*0eq=xs;v|girvzhi4nCc@x^AQE7IiV|B zmDv%?DdMv-99BR?9kaEuwR`d*6}I?=Wg<01qR7k3FR=O@Ngp%^A+9BB3zC$%+k3!s|8zvD=&uc?5seXWIj_r8qqOLD|z5uV7zRkK9=Xj|w4D zUSkg5YzZA7c-i_!!R;_cfH^ZRu)M2xw_thT#I%gB5mp#H<$I;NSw z@(Ybo(*#Duk{I({!QP#Oe1GOYNNE3tb%7`UUoi59dwP8IFBn0E`u~EFL~I<4L}xjA zpgNono+|cNj|n^XrXA60b3jpJ3{hU2+x$99fKZ|y5e!jAAsy|~=;gRs`evG`85>Np z*H1nF2yt3f#ZIb-HP}rSkz6ZFOk|N85z)anK82fnKYKIwO;YQ>@^|C*Julr)-TS`F zZ(GLG{Lc*jt{meI2RpslLlBq{QZB!(fprnZ5hn(szM?Af#S6hkW$iy?&KTufg2-Eq zoV4(iCJbD{#6u@t<|-|4RM5z3Y9t1OB!6M5ghU0%W-N&<+ZJ|-8OHz_vLsM?@st9s z;SRNQ7CG2eXyq1A?S2)8Gv%g-bp7&oexR-7k70QXNp_Ww>B{9jT6Nsq?=|I_^peapI zNvyZH2QoT6n7h^NwAJK-i@WI?^!P>vc)wfbEj77TIC8yV9B+R0BBUDzo(+}?u?9&u zjE+0i-!b`t2txd6MzOVgt>s+l9D&@3n z9E3$+Q`j}IRYN+r5sJkLjx#!v1Z!se;FEZy48OJ+Y=)Xl4Omj8k86Y4+ftjSr=fll z?8_H**ta6|(ID>D0;GQdV+$V*aQn+cCLC`qL$TKD=3(f6AXM4%>G&fIs&n@jC9MZp z@z^>f@UeBX+9E01l__>?KhIDm%tq6}x0WH^@(DMwu9XxjS)QC*j=xZcGCkiqB6|UT zD9ZFLlq6sz>7kY}yh@NNx}O#w_S=O%8ig)Z;mYa77cCpdYOH1ebrma#2=(^ReQ1&JHOs)BKK?l8&dw+`8|qy)nPosH{NTwW{{1YGuFiRZsibY+9*Xv)wRQ&)qmrJhxUU{rctQ`QrP*?8oHl>91P-P(P7?}mpv3Su``@mVTy^(5Zc3cq z?kz^?E^vdSo$+)zZFsbntf=UNUuN`|7|SBz26IM;z2Id`J(^}Olp6Mf>%n0y%2=g# zx*q%714I3L<^{?Idm^@LxtIOiS>WDSLF?b!f;&dZ{EXAhP(g zcAH&IB^6cHz>*E~1SL;(d;1ofH~nmUFwGKf4K)_cMHzx3&@XXwAG$HJlu44b-v?RE z!iNA?DPeqxNM540_3U)WjIz1jgZrpH2Z=ry0Qgs3qSrN1IaIptQ6@#r5`UC;7e_>_ z0ybQ~t8mw7vv!~F0rIg38Xuk0liu!#u?opCWD^+$@Pxo80Y0(Q+8Eyj!1xSlw&~$1 zjgbc9uo3wdKWe5Xfgu^@awCgNn)%ZhfywLo=Yz>EO~#1AgFe&nme?6zNNDHpp?(!D zlS4OJsXNkNkCG+*?oM26hr5eVg%@e$wEEq>Fz6Vg(Bj~fuZVoqQ?3!adu_+%nTp=& znS-{4Kz42diDx|F+3X+41mjLW60Ul&D2dD2@{#A8YTE=rmz>jXPo_MVgQ?e;V;|jH z_`PCq`mS_EDUQ+;p@$*w?InYuqFz8Y?Y!n>!NMy&0A zWPsg>tA!#h6#RISxT>{9K%c6t<~;4HOo@_9!~8GtMn^BHk>z`LrQHt-c7!#ugH0v= zVquYF5f<4RLOPtOB@W4=PvepS*ax1h&bx-ce^AHxbV%QcwKenN4>boXm!JpCb>v#r3gw^ZjH(-u!CnsbT?%7 zg~XQ2Cqg^T?BfCM>p4Gt&K1F}Xt zh)9g&_GHa&Nti>k+l=lM$yOug%U&WvXGmF{pQ%IZd~?q=K|8B^v_uqtA6=6yB&Z9a zDQ*c6B%o}_BOJHYkh>!Jrf!goWU6D_s%t;}c}?BOjY4yBEhK^@=+A;Q>rr(E!5bV2U!P}6@{1@%8Z zpZ<>Te2DLmXlj2DPV5wX#x@~*e*YpTW85X5mK7tGrTbEWj(z6WeMh;R2JXy~wR}bW z;lCp0QTqEO^gHYudx5Duv^>fpI@}L?r?;MzUiQ?Er`cO{6QVNx9`2o6p!PLi^7ME; zjkZlpGAF3OoUo>*3W00L{JI~G++vzTP&*jnpg{Q<&aR&bmtbg9E1#kum6Xqa|*7kYom2Kwr$%sJGPS@cWkqh z?AW$#+qP|WY<29M{=akT+^ktOYt5Tg>tfb;$9M*JV23Ql9vo_KYkASyx6Rtox9l1L zd@8uEkzyY~iq&8-h3lS*qR-m5Zr&mIS9)c|uQvwKzrFv-E_=lXB9LYcVEJomFcPv%WsO|wTLrX#D#BWQ@(!Pl0 z(OC99`(1v*g7REkKN1HziV&8B$32B8J**q~3V2j*Hd|v~`eTI*8my5<8|kJO3!Wl& zlopfFB6)00Q5crg&J}W%w&Z)NN(K*QnIxuR_@;$ed^X<4g48i;Lct>kJ9V|>-ntn* zI0Mvo{#~kk)1>ogX8ye^u9vs=1uBSBY95Df~Hqz8pjD&ak=m$4H>HI4#_CtJ!h!rpbp6mC@l;-t_vUqeyHI=>R_R7d)J}0!> z|J#s$@|M?s3h94hPPNio(t2V)004yZ#y4#iGJj%eOuVAYOkylHmDcIBY=B{iYtd23 z(A;dwY+^?+eb19~qZ(h>&aUIzW(n<&LeKg6b>S_5)oHks-*7e z)*oJd42G4t`OaLIZx}CG`g2u#b?NDaeg%1BAUI=|4 z*-Hp<&2RHtYhMT6lmjx^ z@w2<0!ln%K8+IEkQAVq3wlsOvVoYQX#VZ}OxlKqtE>jb6PEW}p&;XXa$~ikI;U$^M zPPz0)kx{yfbR~GxGUU;gh&PIiH^r5Mnvh9Mu~MR|l4q<;kL>87AOn8-CeIY!r+2Bk zn{@b%o8oqN@|x$lg4)vPl`WvcCKb3&s0|+WrwiQ1qYstQ7AP#Yq^2ywCa26_7$*B- zYvvnmaZRF1cKEn3L)1fj>(PKVKbunIGm9sy3)pf zgzO6StB^#n$_GPPTc4sPYb+MaC9^%7T7k-z82vsB(gz{c@av9Q(VPRoVm+#?#h*D* zYQLa{c~}-Qd|~9ddXi={b19(N572cliB{8csAg8LWCJ7=GlBZ&$lw{4jq*)8vS<1m zR<-^5*PjThmgz^ZwxM9`@TTzKq3Lstu&(~KQG!WJKb1@y<|aB=Pg3@ZvQXUT6!Kr` z(lv7MP-L?R`w#6l_iP=50=ir#OB9Ktm&QiFj=EG}jUH4JL2Dh3DTWAIL~uL4OE+0e#Eq(~z#-O)uKPtE!u z;nDejaT`8BO^FE9T~*WwE7@aPKnHE84*qK8;qcayJ$~4L47TfoaTLItB!_(~r$2$W z&*Op>w5K1bclDB`EJPrK{D#(DeNsHt3Hjra}({;;pkN3_H2ic~7A%JSZ`pYuF zDjc;;OHp2#AdWbZIoDVsp9Lc~3nxzKf|mY+2T7-MG` z^sZ4^qEaaEEvmG0166~k!qFu;hcDs}j$(x8GmqIcK3GD1PMpAO#rZ*6fuFf%38Eyy z3P9Fi{rk2QUudl{N!I8H5N^$Ep@Ic$0odvw(f1llL8a0;^V@_4IrP=4R6?w+rFoj9 z5Stn%9fzB9L-Tc;Pi-$1VIX4qs#K~}=QF-+pLK*4T2_Gp{yPLOgW41NVg``VpoEDu z6Jrg-cRs;C2n%Y~KUIaXM{c(4f#MCe3wu1SvzEvlaZ=S#KledOwdmf1?@Q%0p z!PQIQ^c-&>mCs!Dq!oM&m@mz-z!1znvjmuN{?fMV6`O^#>x~38a->UZ_VD?!Zq0KZ zKz-s+`t(y{$Y4uWs7`hZDZT;@J0A>mZ*=%;ZojlRY(0KF%`v> ze)U$D>dS~*!FLKwo5^I9v1W{qihO&QMJEF9t5x$-ZlbiC2bL;}iJ1=P2E&toGJGn; zy%-!KE!J^$KS0fobx8q(>gULa88DYGiiH*>gUs|Bnh-eS#;6@ zHNN~v4Dx&7=sv+%anI}u=de7^fKhX|V#oo*}Yv zlo=Ig5JpbsfvKh%YHp2^)aVgCAG%$}5}au^Oly%9ea>n6?snX)vtpuQa&%+Cpuee@ zZg0J7=s9PKL0C1*bs3yExahoh=y{ZfV2%CCjNy@sm_r~(mF&E9w51jsfhnH}x-+sk zg~J3<^92=I8m1#*dm|(aju%-clHL090^u3= z+U8>Y#qJ7$9)Z4{i1lb@n`?oi9dfjD;4-&!r+_i$B^&%IebvNl!3nh9mGI1CQMmNuwpfl88ttWh0JF5r68@ z>H}dY`Ms3a>#&jDy!bIUsri>M`S+_8d!Xq|BsLh>zF&92>1FflX6>DzAhFp_VVH2+ zu1NfK22P@^JPv9w&^k7zFzr(uY}n`4E8a{aWqI`B(j>RM65m)&kPE+8$p0LW5L-g9 zY}S9snvosn5r;;YXPls|3t3JOsI@S+&q_7PXUtQ|Xe+gSyNJ_3DoYSk;Z_uL02d(+?X zV55OIw}}SUL2WjA#cqm2!En8*F`H8|u?Qk`bMRZOCzA!D-OJq`v07CNUXXZ`*9P`R zM=R#IM}r9%cY`4#%;I_yvOo5khrG2)Yqk9OVI<-VEYiA~+eYGSp@igJEU}}2o)Wxn z8}=VV$83+i2Lpv#jNx0ejQ8&*RC_i4h&#>6LGLBRWI%W7|0qAUUT!GUrV|U+XS!_*a zaOH|~G#JTYmnN>0r$bsWddlt=KPWcos_5{SViV$<9cl+>Z#C5tUMrcc#8};=_GnLBtooYi|QZ_gkW!1xjoi?a3y~aFr`l6 zbwU|&Ce8GcshcEr2$B~7GeLmKvt=JZB$&oXHb|sL8B`Jieg>WhePs&)&xv+^Qi$%C^~M^G8Lu5L$uX?{{hXgFiik;j~YENafq6g zAu9sgmwZ0l%yuHCEhZBs@CnmHn_e$Z=0sMuYsu)lLuss`_Cai%eobRe7OPw(IjGzO z@jL{Yb<=H;sq#`CzfBiF0w4Cbh?h?At*<{OgW@uWDC?7-hI$#+1)fgUs6IqgHfzc0 zY>jxssdEtPNu}r?;lL1+bv^>PYB3GhE^QTu8%)T2^fIv(G`WBaQJC{6P$0_%g&@^Y z4u9msMy)77SNI&sH!qP1ir6h@rBW^m&~Y+WhNY0bh$lxo8yq1a&wDhLm|Cw*kqu$B z40LIy4W@vXu1O0MuXPEA4x_b1Qyn!qmy2LB?{Jm0tK?8pb2ikOtPuv1>gnbHc){p2 zO*A>FQI9FOoakZS*!3q*OW|vWd8DmUdFS}0GL_+BKkM3BHH)hE$&At`%V}Ea7C2pg zEVz}7fOsQ$kAg`y1;G&0y(=!A`6`B`cW6T_dUwQLpaM*hLBrv(kSAvOoG%uqG3WuIBy|iIT!O1oJ)03*MIhZGB1s3Fr zbadADOCGwu`F2r^zk@iL#U;v|X1O^eJJ0W$ER!}a$SThxZgg(#bxeyI_!K)O%DEIZ zH-TgaOOWmHV`V)cBTbCz9fh{D|F{lkoMhjmg+?BaWYk>=P9e(|%A=rc?3w(m39 z153$)_r?usuh94dxK!v7e>V5b^ZU_67jhzI)FQS6#5wR~EZw~BODiXbTfsMPTxsUy z^RAy?AiK0SM32mzuJzeFsFz3aj}5BdGRS8O0^rI?-}>{-JEw;#E(YZ69aBY^ zn1@Q_v*9CFW zVh|ffv3|fiEhVmZy@Q8eOE)}PuNTU1@;Sb_r9$D|r6evnUrt%x;v%-3`kw_vOiZDA zHI&7GzhZi|JMZVxy_En*eLC`L4SMCl2yqP>5^J`5Cv0M03V2X5bA^5d08JxPr0TE6 zJ9Q8X3~W!czn$YZ;HsDS#?8O8u0c);b(Pa6@3(+xmy`Dc($=cx;nhA})U%O=@)H70 z!gKe36Zj39%nzrWePz*mFUvH7*c9&&mhfv4qV+HkKF^91Iutoe6m(0eY%X2n1oEfx2Syu zr)+`0y|-9KvbitV)g$Kuq!@Q!w&QX|1$P8Twi_>J8Z~tDNJZJuF=|}}cX%cQjPZlv zfA!zcYVY~X+l^^?3KW!66Zo=6-EnxX#PH?do@lWHgk~lS3h{}K{L#G2tg}=>kd||I z>FHTUBoSlo5Dq>|vTE z!a0fUkIj;o$q~}7_A6DKHpn?q)VZcOcm&Uq%~I$Uvgp*-!hBLyxTS^`Y1SZA`m6!g znSK%FUt1lZ1(s24tLo=SGAqlXArV!9Y=|5dTGY z@tM;>6O=!xIx#7HqCaJ02L2^IU~q!1L?`jr>kOC=f$R2q8Uqq#n29=I%3|7c8#1^UYA zTl^7Mhhs$z5Wox};Hltx!_dL9_6E%v0R3 zEEUgfvPN|S?PG)MbNjKE=vIrH{FIe3;3&WygUORaIo`A15ez?Nt)Ps-8`2)3*^z>| z=maa{GXs@Pb!1-L<~-%O;U#$RQRC53xfQuB8NOAyRat!ka9{JXbFl}upmnW5Ks)*Vvm|Rkw5j^@z+1mSAjW75|q*R@;jajWKYd0_I$vf zHc!TMpiq~|CC+`IR+k2rmI1sHFnLqvJYzr@oT`X>3sYv?+2?;r;_2LRH`c18fUt;?rN)Vs#o3wXCbq-q>HD0ZkXnKV= z4~0ZDvDfpN!tuYM{wJ-Ds)LA8V1R&3(EKN+4?3~{5xjNOF~0v4P5<`sdAI0vlYL%x z#dEP;vkNQgj z780N;EaC!$GQ54N#JHH_TF{&GuQdq`(t+y1T!)jbd#~u<}pFG zqBD9ID8YtV@uUg$yW*lU(5-1U0z1ZZ)LWU)WWi%ADotXbXk4Fc5AG?WKRVomUHR&U zg%qZ-r-SJ-64ysC($s~EiwTy|uAuoZ#rmhfxKt1%YIle|O1&Aq&9EGs-S7Z=$9NQ# z6jn5oC3lTcIFpH8MUPrA@*MA_3BN^66KP2w5T1|F4t_LRX~^a>7SG4WtgD_Q#UV<{ zWQP<20yL2eJ2Pq|3Eu|+Hy#hbi^bnUXUiUGuGFyv zs=_dlRSRfv4U2-NCW4bz*a3wN1SZNIiv zc}k*sE^#t)Yf8e%L@I?j5#UC=T2~+nd>$>c{6KrP?ue02n=)X7*y8A_g>U4bE<>fx zn^XNLS)#YV1BM)C=UfB@c!Hu0lr&BNcLU{eR}L>ns!Dld`s;Cz3ndKC%f=8xov)jU zFksRhA)0Z|wYo+3H=@gUb^;!pP>;pH;H-~-Y8&|@q5cqzkusWkzuo=CB?(hPz`cOPUU@{ z45M()PR?OM;zsDv36}4{XVExZD%+_zU}|UTdxQ`agJey^tjDMu8x|PL4zLu$YN#Gg zac^JT1)9~8(h)Q)vlp23<5n>MMWJSj`F4!8;!U>rBliu1XiR19DW*K3>ssz%XzrlZ z>T(ilVxdTbppRZv!VzCpPZu11FculZqk!-oio3sI2PW~mL@}U{#S>!~Cukrhz)*U< zxCP%sG5j&rFpOtuFI$Ed@FG%oFk7y$u$qAmQi%D5op{MqZbv(24&Lx!*2v}}34c;b-T$3oHSoDKtKWgWd49pek zLt5`4Qs$&G#?tYz)%`$9orWSPjDFtp-FZ21nU^{^iD}BF!L^ne!z=uimewXs-5E|? z@OIlw`dih7KMW-Wc!%tnx$FgKC>@Q;%wH}cxmX@_QCM$Z(K28Kqgp?cY-naQc9=nh zh&|$=)|T=u*mLA3QEGFWmidEUg@_(j=Y!nrpQdoI8&} zLX*#V{^7zuO0pT8o48>(q%b$e)P}PbY>*Ji;Kqtt5wWfSR7VPw!`Kerp#>$FSjVD1 zyEn1oWI_Lk*w111nre0&Xwc?3*tPJUG8mY|^^N`$MR&3;3mkI#(&^#pMMFlQ)u%Wa zI|?GWPmHfMb(FZ)UBqjBU#vbRYNJe7C~-OU2rR540+MH5{S=GhMaBRYB+R5^w2rfc z_FbhFTCtA-i&}46Bsk8qZGvSF(5N{7VKe-!ZAbg9lG!Br{tW+#yyfcRYT=Y=hy9X< zq(6p_U(K ztjidkM$kB>?`bO@Z}U57#IO6Bxt+m99z6_(Jkcw%ZE%=mbvf!T(S=1??l_skWfC!6 z<0npNUtLzRE@7FZ^|E+-+1wC1OL7HFdW!S(De8$!WBaormcH_MW=SlK2|2qJHzJ>q zDq5onP)IK=bZ^YF^t~eAnY5$w`{N=FpK4^T$%kvgIr}1H9wbR zZmn7R{e)BH=}nr+*H|{Eeb+A{h8wz(m#j2nfK~?CQ9K$;{65Zemx)n)zz2|bpvTXvK-q%!c}2fB;1?K4va&bR+O*|=0usSt&VXNHWTOV*m^?9ezvJe$rFiV1}DnC2tXn) z1KE;xekCl(%Bgs@|8SUpW0lLtdWPM%vg{2#t=i~&d)x^iC@b6aw|wMNI@|Qe*%=^6 z;|St;_Wzbqif%vi3Eq^Zl6E)H+9z$EWWKo(lD`fh_p$;9TFS&9pihdDCZ83#eg2e4&ym1V(me zr1td8c?L5=B6giGe^hAtfEZv(0d<+`Fh>8bu7VTh$GvbgeBxhGqz3ruTFnDGZ?4bby{>^hk5gC?Yc3$5#XC@0}(3o=(- zyUzILDQMeTTxKDsEcr=eDla3q z838_;pIx}C*~QLY_)yLWyUwN`yw6O^-5D}u6LG8$sKevXS4>Yk(1ddng?WkG(k~7y z&`UzSKchFWBsJ)3yg2HDl#~2mdYSmZahducZ$*^mE7hDzy{sj_0HfBE2Goe)NzjNyqY%)p zN@1sc8>-w#cZ_e7S*RRtPS9s+k@afCPI(}y*Iek{_pB#EW{OB9?=|QeUUH4Tkaz~K z*Igi;-`}|IP`{H)@11rnJxpg6+Qm)cS3M5ZMUu&(x#!c1mHM~Dw&%qC+st+9CiN_t zx^eC%`M305c>y*59R$uk`u{ulo!_Z+Cl~IX+D4a_n&bgGwFtw{m6zbBxhn^{tI$@D z2=Q>pRODU)rHKmt2L!_%rOX#xo?ep0zlw1njkqA~6c8d^!;yB`0YXtjETdtLYZj7@#K9xF=i2+v$$dNTYGsQ!T&38wBw;Nw0khstDzRxOlfbe&PprTCN@8W( zR@S!sxFjEId`Y!k(%BqXN@!!pW{oR!e^s+WzZUawzNLa+kv3MwZPF|`a;IIz#o5A% zs~_q04~8L{=bi2%FDxmO*yr?1REWKyc)XX5Ret=1s(!j?MfT4tbFUW4AgC%=1CEncd;5chU88@|&4Ln&HFSRj$tr>U-(rdEPNy(THTacB4qxv+? zOu%42c&+mmLtftxwUwG$1Lo$hsIv_=vs}L)0BkLE!T-Me&m2Bb>%?e3B_NCk-l(gu z7zlV<0AfOc$!Xncl7&CF6afm2SPMR3gFH$Bx{9RXcuHztfG*6MsT)>;#j4E4m}N|h zC2DDS(umXcii-|aGytZk@aH*3r|V*o3~_sUlBs*J8$)6^~?WvqIGH{l?F&T>**Cj+Wxqo1m)h$_7E5 zu_NZ)DC@trr{~9MM&}*2X~x(B)tiVj11~i(1O%P?IG-*TXg^Q`l7J|chNX}1(OHZZ z*`~3sG3x-zQumzt=5UzpYkXz`&B>#WLyV^LA~(Rrl;yG3iT`|}*T$o2civkT2WQD< zzzUUhmEy$sb^s{OMO1oYQ&e7bGx+=DBC=j-uKWpXj3eNDIZ@#vrqO_n!*im0ITB%U z*;aMZ)r@2X$`0k}8QEz3B1{P>JrvUiR0;P8U^wxco#NQB~W?;3S{_^?2n+>C|3 z3)+kYw}hxx8B>f7a03!~y_aj}FE3#i5i{5m6IH{g_~E`>v=GxYMfI-qXJ_a(dtR(m z2aH(h*ImwSOP|RNo*xcQ2%K%8q$)Rdequ&)rEUs_(7e0J0o~u7G7g}v5L-2`D4^V- z&fGcztMg!CHHa=sHMoBYS##HrAv`I?ajIsDW}Y&NFsL-`;nGX zB^B8avzBcu-c0p$D5a`2)8FSdR zY0*mkKJyKJJNqG`(<2G~YAHNda*Ic*60(>l`c6$Vc7YvxhRO~mf?EJ)(-RnWPBE?7 zk^y$0W%c!K-D!jm)6_T$wSlEWE){ypTsZ(9$0h;xpfLjTU|VYxr9bJEU&2{W6cOE) zfuOP01)NqKMdzJKv(B|gQ=MevXp>{+aQJ}EbrGHG;gUcms$KV9)}}A#(AewA$m5VA zl5lGf1^OIqkz1G}Bz4uJ{dkXu`n|vD?gjyksLLddFQ8Y4;NIXYbP5->Y9DomPi_p& zpQckVEGOoz6U{d1Th?nGgg}zRt-kQ;vEc^^6 zVCJ&NK~2CiFa$Ap(P9#tFAfkz%$8uspk&Q}%l=Hm#ooP|Ss=H*!ya1XnVb)N0Lvo6 z_X6F=DQDsYmwkjhyLv!O`RtEaQRlj5z;1^(4|b<@$?;#{reg71B4r!tG~`|NQWDYu z02`s}8-KjpdButf$=w{O#dP!&AT7ks{fOBk8b%fy9{S`AddI9~qzjPWQ52f#@D^6` zwnSp6zZ2`aqbWjJtvK!A)m2^2&5NzOl;pAQs`i_pmcmLmdOtI^5nfVaw0ZlB$|J;J zK~cBJcCOVPQ0W|kxWLvmNcl#itO*P<0@@at;*o2y z%1LplUjKo=h9*tsm2;r9%XK-*LIQW2)6?UiS-XBN+mvY_s$$C#YU4l02@vd|Pb4}A<}n(yG-)6}xaE>UQ`6mh{ebJYoH7`hFHRr*e9cq$ z7n3EA$5+*|9}cU37+5A#fx@8}R1cU9+A+^y5UsRKA3b@S72E8u-4da@V}vFMJ2Sz(bh8Z;F$$ z-n`oTS+p+LcIkK}6Us4&v((d6oP1z3ZNn@r@o8H@9H^DwSIR36@bB)C7UJ9=I8^9* z;E-Obx6SLBjxN2nvB(?e=%UbKFEJK;AYPga=!1RoA)Swl#a7FVMIrpnx8JWid7f>k zvtDf4Z|QHn>?$NRh`Vo5LJY>7&W=n%1KK*d?JItMequ0do)#f!4UX*vI8XI9ACc|g zcNk&OB^E{y6@yW5;6$6>zuvS@bv1ls-zDBw5A`>3FvD370UNvkJ0zw#GhZ(1l<+)K z^m=cR0lfy+TA8+A6j|gN>V(Ee0-psi=bbBidnU``vWe38ZGa}~0`02wUivev)*l5@ z@>yq73uFjE9fqG<_-+8I6*^LKPCw9FkMm`GvTaq6y+99HV7Xb%UG71c;k}A>s}3pD0Es!IpL3IFo{|(9*-Septi8N<-q3U@qrBYx;PO3e73Hj2JP8 zIqS2Z*Zc*FfUJNLdK7d%S=GFf<~<5y{mWnJoqJO(o*|LHsbnE?)}ld?5}&7j!;m() zK<*QQ5EZiz_OLg_P01GC9%hQil3t^AYZ-FudTzKGfi8A+ZZ)7j;G%HoKYuf)1AY{fKg2R8|= z4to{$D&xO7DK?22Brl-gHRfa-j-?-3gm)s{e8^qBGcs!C&zE-Dn}60UY@DjY4%aNa zO`-}SH2HI;V1`506%k%FSQJUQ6EZBML>5gc0lgg}t|Kumb*yepD{?zttH(Gt;$;*T zGiz@Cx_Ihz;pG-b$79|+sSRirUBeaq6nk0odFaxV+xF(*#rBNfp+5yJ--30H7#X9*$cN&u@Sw^Zk6e0- z=ihx{bP%W(T3Q&YFsOACnw&dwieB|i`*CNRc29YTOD&(?pnSnHoAWMuX?mw`H!-7R zcZ!={9>m2fZ*Q$Do(uCY7tf?~DOXYX1+=t^2=&fMc_S4Ngs@%=1)N_n*01+sB6&u- z)JO>hJ)YG2X5>7$yaK%cUd*aUb`7@{#@pp&=06vsYJC{D-896xFRzgL+)}rU&V|P2 zJol3rMEn)RQV|n>8;4V($)H`J;C^2(%8gFo&AIg=CEGa-W8zdHBC>o-k83r_2cD?Z z&CYJe0k-@g02TySL(`nZ0?wN;f3h2&06$=eE+2oaU0`@~IlSsgm@}F2TXd2x7&x-` zj@fNow!4d=x32f)ME~Tn2{kr9y%WFl)aN#U+BOJ0EXJDX6R%fman$7D&FPlVR4xBh zYSb!HWV^OwzMeTaScM?IZ(l;b0m3hiMm}V+JwU)@G3nslX#ZWURORZ$QB2N$!2MF(_8v6^r|Nbi(jIJ0lYx9OiI4u z)^1>!dpDWvrGFNAE3=XHRo+E1L~C^2jj>m=31jIsi3*%wga4d9T2dl+4Hk`RIt?$e zS6KY>gQQPsQD~P+GO#a!$PV+dxVos4k$`~+oo}8Vl-p9GiaKH>0`VerZOf2x z&&WL@NR!-K#e^XspgZHXQRhcoZG+^ngaqGy#CIt-<50GEeY^ISYXS8y&7qY7kHn8F z#)zK-tJop;&sf9VdOIQ4!eXtccf;hc0bxq+5)T-|pIB$}91|JBvcTK%gY6&Hc)7TO z8j(KVdKX0{y8oX+fO{`Mhv0yPe}w>$eS8 z&Hgge!-^tDPw#^Z9sutm3a3d`8(d5PQQKuZuN1J%TeHDk9}u-&nC&7YxP^(o)UX?T zzv4SSxbnW;ycC|=kG}37VE(tCTQu1)%ka$O)&B2kP%t|w*t+%2 z>m&BRS1zbQ{_VaEkm0s7>0FQgY`t`z{A}`&IoFPeB%{pxX6QR7Q=>{aM6rAbHYw-5 z^Zu`ml!Y`v_Vr&6hzI_E+Jr?s2e7_RlqN+*xGt~Fw>j99L1ID4_?Ohb{z8rw!^1x= zztw4i1huiO!>tkr_ zr0r#_b3amg@^w1jBJ3daM;%Qs!F%=~81_A+7{|jr8W_k1trDAwDD;c$FM%>#1sL7N zcsZBYF%$E;2DMt&iduLYvoG62t~|)i#majmuPp~?!7=vE4{-xw-Q4VY)(q{?X-3TE%R#`451jj5O$j7WB3@xozn}|((q0-a=%-J|?xJ$Sv zR#;3#_@d13!n`i*j2+VGjmF)I(AHccEYBMJy+9Teq(*5Vy8VGu~Xr<|8-|v~nx<7K>hG?US%2io{O1CsLl;#^^8j@TB26 zIz7S@U6$by>qx4f@=@m7f3xpPm=6g4fBAmG|I4?S<3vil@r6!gPND$He-8n~bA{Jc z>Ey-eQk4F&`x5i0A9~j15^cFM>oQjY*P#9~@WT*#gAmDNg%M^2zrOgsPt(7@K7RcG zF+3+(+M=%eNjp+X|0H}Q=+YOklf6t&?uLpL5z+f&nB-0wMCE00h` zCjVb!3J|S`-kHfXDY*Vvolf7TYm7mW+}Q3P654J;4g0me9>w?pc70;12Uu^VO@2GU z&mk&llq#nKZMi{_Py=_SOrKyL!h~e50#Q%+&I3M@$Hc2{8KzT0fxRC?Uo4w|MIXNt zx8)iv_a`2)+gsIR!YpI6C;4lR$%^_@rdgZl6Q7hvW!X8g(U)h#XG<~Jhy$D?Lr?(s%o1P zf*2B4*7ik7!kQJ{3K^b)pOW<-FdZtiQ5{Z%df!&Zs;fl)mxM)d5RyBIVQNT?(2#4NL_kU*= zUW?W(ZPzSOVIOjZuP6$z{^hLvQhk&VHbEe&;$MQjfmF_3RIXmaME*=L?rNz=c!h^2OB71la2QL2`%{ZHxS!+OsSa@rfm4VOdg$N%2AHGvogv5MhPk` zzq+MUrJ*|}*45%Ah~$#M!HPQwFLbTdx@M1Ze*M1vq1$wk2~BZdk_98tZjX&XHOuudfQb#TY!Rkk9O+&)~NYe*^h>!0;i&i}ZZkoDph|&B)$|RncOvF|_0( z)@Ief?%k^RRWh?xmZ2eH8*qd3R$Am@;!;R|S@w&!yzshTO+1nvc~x}mdop^7syHt& z&`hALB}Tq6;VssVa3Vm4CclbU4)`ePEsc*>F5RG(G81yXr0*d+3QOD6jd<+bQ|=qe zEg)^3(vekM&8t~`7_6&u?JvtM4X!Tq3r+Na`9rvL6*>X(g+Y1njA|~Y@O_=r%c=bm zb7xD!z|M_2UDk#KFv!Qz)f(Nub;S_(_ZH5(k2%xZKNg$NI7_gGQMgwEar<7ypmoq@Xyp^l5ENeZnT>EQJPd zGy}S|R<)6>1>6&zOhaVb3!3f&DF7%r9~+wFB?NhX68cj7Wfn&+5X`wTFyxliNA^aE zn)m>|@%5i>tw;H0{{;4rfcgaa{{y*t^-u}*_=(mTSU{aT4dEoJWbomp0ROl++s!?j7<0K zNWbD!X3_wdslzJbS!l9=YDT)HBn}Sk#R>Qm*AiwcW_XSAczSj1vnh)uc*k~8jKJw| zR~qfYM_|#EGkW8?3r%AXK;YyyIiz4WNV#~N9WkADoYuIbN{0LQj0@Q6!0Xn>fH$MI z*~z{n5i;mkz{;HLWqTDfsIq*jN`k^9tgPN?lfJpvdA2DRM>DA`LU*${lLs`o;u()T zjastG?_pI9*6uk)Vd}|{^2uSyRTSvU7ByNnRp9$;Hb&9L0iK5;=-xIk9hUNsW9c;l zM+9|jZq=Vi67F<_8f*bO==TUDG1y8hvDO?xe4gsyTBk&`HUJ;!bn&f&Lix_@z>$kAsnBnnC@W{OA4LQa}zN`~Z8PGRtJX7&;-g92K*81-14G zw?}^c6?#H)6e5ZLkxwUhwrlC`z0l8A^HLDV)P4|&nBzKJivJPMCwR2Wqv^fTPt0Id*@-!WtqVF=%Ao*Ju~%rebC9~ew+)m|AH_Cvt!HR z^K9sS^e~i)h;`sVv49&&^j9LTDQ0URO>Za(Sp)(C7Q1FJ7;&;NLn+AciH`rGkY#d$ z+Dc2acu>bl2QR8n(!=42F)&;l;Bm&+>|~5mHAaY{jntv*D~i>Wm?S&vX{fUEO}GYn z&wE?nj~uT!1jIrrwDn{2D>GD%zA|d>!T*p~6j$j;Qt~j7OJ&8Wk$mEFI^m8rmzQ_X zPXHRtqgbj%P$y(WJRlP6IW7iUu_n)REU=r}G1H$lxHgnj{d_AqZe^yYw%}2~;?8Km zL@{0{i?Oy+QD9+rnKd(1=R(Dz^gGFH?L!Eqf&)SBvhFas66s|{~4NB0J3VH08}LoC;7pt{?To`2Wj z`tA$Q7yTsRX9CqaC80xNomy>AS`%T`+pMI6cSVTSgLo?}Df>TNoq1Ff*B-}XOj#5H z7KjB#mas1ZPY`5_2LiGNN}E7{00o4SO3+{{V1UT>s9_TZ;)W;+h><0c3If6dMB)Mn z0?I>u8huqGgrz7_+&URO!6E0&ADR2f?|1K=$;{k)?tH)VIO}^qHKNAV^sWyPd|vRx z^PQ$DH*BAJ8f5n|)rfn7hV8vB{gNC}QJ((1_2)EGi*HRnd0-?)KQQ(EJ&T>MvFW}_ z)31p-$TQ z?1>6awB;{splC~gq5Mv}yp%dMY?UvWIOX~f7<*m1&T;5+16_AC!1{;paBQb-#5m&l zW0RasrJ9ljtyp7k(;zw}0bLPIb>qJE;Zz>+CrHXus|yyR1{;F!j@aPJ zbEL=tCb_4i^guP{L+C_J!hvF8+5kQHj%}{f9}Q*m7f*;c7Y&@APWtF>u>`$sFKLd7 z9e3ztUaGm~?D?C>^Hr1&i5=({|92Pj%$}9T?>}C>S{UMzs@S{@^NF3WtTa7!%+5n{ zO+41j+K1jdGGJY=UYm9zn$ElhzvB~z5w+L}5?!EJ%dahDUj4(FtI{RiitxOpbiFQgP& zc=l+yxHpdVlEjI>7ixc|;EEwAqcD&3A$|UHwi`8LpV>9iBRzO^+Vz zTkxY!WNb8vsb~{%-jMA)Gput>7QzzH=Vxi>#?cAFxT}Y;uct1l$TQLu3|h(i2Dw7! zE$(@7l(#A+i|t~ju*pcn@aUtypT&QLTe>5(XV4*|I&x{8xQ+C7|9!gNO#SgBi1`g;_u?vqs!SA8IR|x`u}_qz3xPR zbBM3YP)l3xGqZ3xRuTXH;^fIO0VTJwRlrJ~?6PaZx0CoI9)|r>=5uEcru{iF5<$*u zY9i#D+n*{*;?L%O)ay!8ak_PAb(GW?RqETL zj{;dWUW!~gc7_FgEeCJcxC7`u%ws$>UfTz4|3X3PDYDNJ7A&m=KyMX2@JzF+cH-_P zQWA7GYk`CxjS=7>@JOvYu%|)(csNwv3O(@IBFg>L;6UAKcxfO&W>_wdLb)J7RooX) z9%R+o0bd)ux*|YGT2>j1i)@xP@fJ%skR|1&$W=%iEpVTjf#;v zErH)(z@Zzq%E}5ZH~_2OBy0PeYx4z^E92<`GOGcoOOeN>W;^K2bNdFC$Op4{8faH1 zXa^qb;28m{GU036vgi!H;{^aRiE5|~ZiqHS?t}nsNLAbokf|L*5CH*2xPgx@h5|Ch zT?nv70Odq*Q?mvb>1ibG1?^Q?(Y5J*2ZI`LAiq%oq=IPXtq9057=}8j25{=tHzOdaAq04U3WJGF zHb8)Eu@nl0M?mix5VQrHXwn1Vg*{Np7tn@G>2wf+yn)qeO%zHG5k)Z_0swIEkP2L< z)fp=kN*4i!7Ql64mukSEYkgE#5e4TZ8oL`*D!!E(Nx_UaSv j+6D+geLfC^M|+mQ*Ow$yL@ceNaI6S{mE76Panj42;u diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 78cb6e16a49f..a78b3dff983b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=19ce31d8a4f2e59a99931cc13834c70c0e502804851c0640f31a1af9a7d5b003 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-rc-3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a936707..ef07e0162b18 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From e1551c93e361bb3fe753e80df2b9b0c6bca3c0f3 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 28 May 2025 19:39:15 +0200 Subject: [PATCH 096/162] Update Shadow plugin to 9.0.0-rc1 --- gradle/libs.versions.toml | 2 +- .../src/main/kotlin/junitbuild.shadow-conventions.gradle.kts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce36f5710107..e9bbda2cdaa1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -104,5 +104,5 @@ jreleaser = { id = "org.jreleaser", version = "1.19.0" } kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } -shadow = { id = "com.gradleup.shadow", version = "8.3.8" } +shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc1" } spotless = { id = "com.diffplug.spotless", version = "7.1.0" } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts index 6962f80c3ca3..237a7e87f968 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts @@ -39,6 +39,9 @@ tasks { exclude("META-INF/maven/**") excludes.remove("module-info.class") archiveClassifier = "" + from(sourceSets.main.get().output.classesDirs) { + include("module-info.class") + } } jar { dependsOn(shadowJar) From 905a7bbd6ea7e8cc5eeb32df0fbd24a9c491d32c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 18 Jun 2025 14:25:40 +0200 Subject: [PATCH 097/162] Adjust test to behavioral change in Gradle 9.0 --- .../tooling/support/tests/GradleMissingEngineTests.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java index 846a41c6adf9..249675770f47 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.reporting.testutil.FileUtils; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; @@ -41,11 +40,8 @@ void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles o .redirectOutput(outputFiles).startAndWait(); assertEquals(1, result.exitCode()); - assertThat(result.stdErrLines()) // - .contains("FAILURE: Build failed with an exception."); - - var htmlFile = FileUtils.findPath(workspace, "glob:**/build/reports/tests/test/classes/*.html"); - assertThat(htmlFile).content() // + assertThat(result.stdErr()) // + .contains("FAILURE: Build failed with an exception.") // .contains("Cannot create Launcher without at least one TestEngine"); } } From b664788787684d3f896a65f0514ac342b6f2850a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 15:52:42 +0200 Subject: [PATCH 098/162] Add testRuntimeOnly dependency on junit-platform-launcher --- .../src/main/kotlin/junitbuild.testing-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts index 2ba0f59d1152..91982c4896ba 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts @@ -200,7 +200,7 @@ dependencies { testImplementation(dependencyFromLibs("testingAnnotations")) testImplementation(project(":junit-jupiter")) - testRuntimeOnly(project(":junit-platform-engine")) + testRuntimeOnly(project(":junit-platform-launcher")) testRuntimeOnly(project(":junit-platform-reporting")) testRuntimeOnly(bundleFromLibs("log4j")) From 54c79a4a639e7a31751ac246bc3d30ab56cff0f3 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 16:21:36 +0200 Subject: [PATCH 099/162] Force Errorprone and NullAway to use Unsafe-free version of Guava --- .../junitbuild.java-nullability-conventions.gradle.kts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts index 1d09a23998f6..87c01e957fe0 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -11,6 +11,14 @@ plugins { dependencies { errorprone(dependencyFromLibs("errorProne-core")) errorprone(dependencyFromLibs("nullaway")) + constraints { + errorprone("com.google.guava:guava") { + version { + require("33.4.8-jre") + } + because("Older versions use deprecated methods in sun.misc.Unsafe") + } + } } nullaway { From 1f075926b83b02e03ab9fbbb9be0a0f215008f8d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 16:53:25 +0200 Subject: [PATCH 100/162] Revert "Adjust test to behavioral change in Gradle 9.0" This reverts commit 905a7bbd6ea7e8cc5eeb32df0fbd24a9c491d32c. --- .../tooling/support/tests/GradleMissingEngineTests.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java index 249675770f47..846a41c6adf9 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.reporting.testutil.FileUtils; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; @@ -40,8 +41,11 @@ void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles o .redirectOutput(outputFiles).startAndWait(); assertEquals(1, result.exitCode()); - assertThat(result.stdErr()) // - .contains("FAILURE: Build failed with an exception.") // + assertThat(result.stdErrLines()) // + .contains("FAILURE: Build failed with an exception."); + + var htmlFile = FileUtils.findPath(workspace, "glob:**/build/reports/tests/test/classes/*.html"); + assertThat(htmlFile).content() // .contains("Cannot create Launcher without at least one TestEngine"); } } From 5168e6b7c16bab4eda88c7a42dc6387f99820e43 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 16:53:27 +0200 Subject: [PATCH 101/162] Revert "Upgradle to 9.0.0-rc-3" This reverts commit 336357c7d6c2b9e6c220302e18665575b3e59caa. --- gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 35551 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 delta 37256 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a78b3dff983b..78cb6e16a49f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=19ce31d8a4f2e59a99931cc13834c70c0e502804851c0640f31a1af9a7d5b003 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-rc-3-bin.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index ef07e0162b18..23d15a936707 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 79df499ebf4d36c60226e190a173c91e9e2a33e2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 17:00:05 +0200 Subject: [PATCH 102/162] Add the `eclipse` task to our CI builds --- .github/actions/main-build/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/main-build/action.yml b/.github/actions/main-build/action.yml index 338667f8d39b..e5d872267fe0 100644 --- a/.github/actions/main-build/action.yml +++ b/.github/actions/main-build/action.yml @@ -4,7 +4,7 @@ inputs: arguments: required: true description: Gradle arguments - default: :platform-tooling-support-tests:test build --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 + default: :platform-tooling-support-tests:test build eclipse --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 encryptionKey: required: true description: Gradle cache encryption key From 1d6269c7d4898c2d44f7a64384bdcf051439b0a7 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:02:42 +0300 Subject: [PATCH 103/162] Delete unused code --- .../jupiter/params/ParameterizedTestIntegrationTests.java | 8 -------- .../params/aggregator/DefaultArgumentsAccessorTests.java | 3 --- 2 files changed, 11 deletions(-) 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 cf042b8ee2ea..133d7390530b 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 @@ -525,14 +525,6 @@ void reportsExceptionInStaticInitializersWithoutInvocationCountValidation() { .hasNoSuppressedExceptions(); } - private EngineExecutionResults execute(Map configurationParameters, Class testClass, - String methodName, Class... methodParameterTypes) { - return EngineTestKit.engine(new JupiterTestEngine()) // - .selectors(selectMethod(testClass, methodName, ClassUtils.nullSafeToString(methodParameterTypes))) // - .configurationParameters(configurationParameters) // - .execute(); - } - private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { return execute(TestCase.class, methodName, methodParameterTypes); } 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 49ec02df7874..4d223f526a76 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 @@ -16,13 +16,11 @@ import static org.junit.jupiter.api.Assertions.assertIterableEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; 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; /** @@ -169,7 +167,6 @@ void size() { private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationIndex, @Nullable Object... arguments) { - var context = mock(ExtensionContext.class); var classLoader = DefaultArgumentsAccessorTests.class.getClassLoader(); return DefaultArgumentsAccessor.create(invocationIndex, classLoader, arguments); } From 23e3481e4f5e260664de9689771fd7a9f0aedb85 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:03:05 +0300 Subject: [PATCH 104/162] Suppress warnings in tests --- .../junit/platform/launcher/core/LauncherSessionTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java index 212416b25191..c66f1d3f487d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java @@ -84,8 +84,8 @@ void callsRegisteredListenersWhenLauncherIsUsedDirectly() { inOrder.verify(firstSessionListener).launcherSessionClosed(launcherSession.getValue()); } - @SuppressWarnings("deprecation") @Test + @SuppressWarnings({ "deprecation", "resource" }) void callsRegisteredListenersWhenLauncherIsUsedViaSession() { var session = LauncherFactory.openSession(launcherConfig); var launcher = session.getLauncher(); @@ -112,8 +112,8 @@ void callsRegisteredListenersWhenLauncherIsUsedViaSession() { verifyNoMoreInteractions(firstSessionListener, secondSessionListener); } - @SuppressWarnings("deprecation") @Test + @SuppressWarnings({ "deprecation", "resource" }) void closedSessionCannotBeUsed() { var session = LauncherFactory.openSession(launcherConfig); var launcher = session.getLauncher(); From 86aec13f3b8efaf74ce501ec6c09b6cebb7a4c69 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 17:07:11 +0200 Subject: [PATCH 105/162] Remove unused `idea` plugin --- documentation/documentation.gradle.kts | 7 ------- .../src/main/kotlin/junitbuild.base-conventions.gradle.kts | 1 - .../kotlin/junitbuild.java-library-conventions.gradle.kts | 1 - platform-tests/platform-tests.gradle.kts | 6 ------ 4 files changed, 15 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 3fc54f95b223..369f48f8541c 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -548,10 +548,3 @@ eclipse { plusConfigurations.add(dependencyProject(projects.junitJupiterParams).configurations["shadowedClasspath"]) } } - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitJupiterParams).configurations["shadowedClasspath"]) - } -} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts index 4bd5a4660201..2e20790e87b7 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.base-conventions.gradle.kts @@ -1,6 +1,5 @@ plugins { eclipse - idea id("junitbuild.java-toolchain-conventions") id("junitbuild.spotless-conventions") } 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 4b5960dafc3a..6b5be1377708 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 @@ -8,7 +8,6 @@ import org.gradle.plugins.ide.eclipse.model.SourceFolder plugins { `java-library` eclipse - idea id("junitbuild.base-conventions") id("junitbuild.build-parameters") id("junitbuild.checkstyle-conventions") diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index ae184107e8fc..f5ca7e4eebf6 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -153,9 +153,3 @@ eclipse { } } } - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - } -} From fd5d6dd2a815b7f52f0d9aedc3975df6888f2f8a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 17:07:37 +0200 Subject: [PATCH 106/162] Remove problematic cross-project configuration resolution --- documentation/documentation.gradle.kts | 8 -------- platform-tests/platform-tests.gradle.kts | 4 ---- 2 files changed, 12 deletions(-) diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 369f48f8541c..b0327e7de14b 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -2,7 +2,6 @@ import junitbuild.exec.CaptureJavaExecOutput import junitbuild.exec.ClasspathSystemPropertyProvider import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile import junitbuild.exec.RunConsoleLauncher -import junitbuild.extensions.dependencyProject import junitbuild.extensions.isSnapshot import junitbuild.extensions.javaModuleName import junitbuild.javadoc.ModuleSpecificJavadocFileOption @@ -541,10 +540,3 @@ tasks { rename("(.*)-SNAPSHOT.jar", "$1-SNAPSHOT+${buildRevision.substring(0, 7)}.jar") } } - -eclipse { - classpath { - plusConfigurations.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - plusConfigurations.add(dependencyProject(projects.junitJupiterParams).configurations["shadowedClasspath"]) - } -} diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index f5ca7e4eebf6..ae6e94d3cc5f 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -1,5 +1,4 @@ import junitbuild.extensions.capitalized -import junitbuild.extensions.dependencyProject import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.internal.os.OperatingSystem import org.gradle.plugins.ide.eclipse.model.Classpath @@ -140,9 +139,6 @@ tasks { } eclipse { - classpath { - plusConfigurations.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - } classpath.file.whenMerged { this as Classpath entries.filterIsInstance().forEach { From b8b68c11cb749a33d94568617285c134d815db3d Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Fri, 18 Jul 2025 17:29:59 +0200 Subject: [PATCH 107/162] Exclude generated Eclipse config files from `checkstyleNohttp` --- .../src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts index 324064421b67..1af6c1f68e5a 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts @@ -29,7 +29,7 @@ tasks.register("checkstyleNohttp") { config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleNohttp.xml")) source = fileTree(layout.projectDirectory) { exclude(".git/**", "**/.gradle/**") - exclude(".idea/**", ".eclipse/**") + exclude(".idea/**", "**/.settings/**", "**/.classpath", "**/.project") exclude("**/*.class") exclude("**/*.hprof") exclude("**/*.jar") From 28904642eec516b0b30cc2ec6232481edb24e12f Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 28 May 2025 19:38:14 +0200 Subject: [PATCH 108/162] Upgradle to 9.0.0-rc-3 (cherry picked from commit 336357c7d6c2b9e6c220302e18665575b3e59caa) --- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37256 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 delta 35551 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 78cb6e16a49f..a78b3dff983b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=19ce31d8a4f2e59a99931cc13834c70c0e502804851c0640f31a1af9a7d5b003 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-rc-3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a936707..ef07e0162b18 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From ca1039864428f769ae3d0efd9068c5dc0443ba6e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 18 Jun 2025 14:25:40 +0200 Subject: [PATCH 109/162] Adjust test to behavioral change in Gradle 9.0 (cherry picked from commit 905a7bbd6ea7e8cc5eeb32df0fbd24a9c491d32c) --- .../tooling/support/tests/GradleMissingEngineTests.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java index 846a41c6adf9..249675770f47 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.junit.platform.reporting.testutil.FileUtils; import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; @@ -41,11 +40,8 @@ void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles o .redirectOutput(outputFiles).startAndWait(); assertEquals(1, result.exitCode()); - assertThat(result.stdErrLines()) // - .contains("FAILURE: Build failed with an exception."); - - var htmlFile = FileUtils.findPath(workspace, "glob:**/build/reports/tests/test/classes/*.html"); - assertThat(htmlFile).content() // + assertThat(result.stdErr()) // + .contains("FAILURE: Build failed with an exception.") // .contains("Cannot create Launcher without at least one TestEngine"); } } From de703b3ab4554907d1f5768266cc337e411b5d28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 19:08:40 +0000 Subject: [PATCH 110/162] Update jackson monorepo to v2.19.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e9bbda2cdaa1..95bb776a533d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ assertj = "3.27.3" bnd = "7.1.0" checkstyle = "10.26.1" eclipse = "4.36.0" -jackson = "2.19.1" +jackson = "2.19.2" jacoco = "0.8.13" jmh = "1.37" junit4 = "4.13.2" From e6cc57576a64c001e330a0ff7f11aadf75f1be81 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 19 Jul 2025 07:24:48 +0000 Subject: [PATCH 111/162] Update dependency commons-io:commons-io to v2.20.0 (#4777) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95bb776a533d..097c3baefaac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } 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.181" } -commons-io = { module = "commons-io:commons-io", version = "2.19.0" } +commons-io = { module = "commons-io:commons-io", version = "2.20.0" } errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.40.0" } fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.27" } From 8e97a8b9e9355b673bf967501fa309719bd332d7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sat, 19 Jul 2025 15:34:39 +0200 Subject: [PATCH 112/162] Log only once per implementation type for `CloseableResource` types To avoid flooding console output with warnings. Resolves #4776. --- .../release-notes/release-notes-5.13.4.adoc | 3 +- .../descriptor/AbstractExtensionContext.java | 35 +++++++++---------- .../descriptor/ResourceAutoClosingTests.java | 19 +++++++--- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc index 54b791e7d66f..d5d027c47759 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -58,7 +58,8 @@ repository on GitHub. [[release-notes-5.13.4-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Log only once per implementation type for `CloseableResource` implementations that do + not implement `AutoCloseable` to avoid flooding console output with this warning. [[release-notes-5.13.4-junit-vintage]] 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 d19c71845461..c028255b36f9 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 @@ -54,6 +54,8 @@ abstract class AbstractExtensionContext implements ExtensionContextInternal, AutoCloseable { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractExtensionContext.class); + private static final Namespace CLOSEABLE_RESOURCE_LOGGING_NAMESPACE = Namespace.create( + AbstractExtensionContext.class, "CloseableResourceLogging"); private final @Nullable ExtensionContext parent; private final EngineExecutionListener engineExecutionListener; @@ -86,42 +88,39 @@ abstract class AbstractExtensionContext implements Ext .collect(collectingAndThen(toCollection(LinkedHashSet::new), Collections::unmodifiableSet)); // @formatter:on - this.valuesStore = createStore(parent, launcherStoreFacade, createCloseAction()); + this.valuesStore = new NamespacedHierarchicalStore<>(getParentStore(parent), createCloseAction()); + } + + private NamespacedHierarchicalStore getParentStore( + @Nullable ExtensionContext parent) { + return parent == null // + ? this.launcherStoreFacade.getRequestLevelStore() // + : ((AbstractExtensionContext) parent).valuesStore; } @SuppressWarnings("deprecation") - private NamespacedHierarchicalStore.CloseAction createCloseAction() { + private NamespacedHierarchicalStore.CloseAction createCloseAction() { + var store = this.launcherStoreFacade.getSessionLevelStore(CLOSEABLE_RESOURCE_LOGGING_NAMESPACE); return (__, ___, value) -> { boolean isAutoCloseEnabled = this.configuration.isClosingStoredAutoCloseablesEnabled(); - if (value instanceof @SuppressWarnings("resource") AutoCloseable closeable && isAutoCloseEnabled) { + if (isAutoCloseEnabled && value instanceof @SuppressWarnings("resource") AutoCloseable closeable) { closeable.close(); return; } if (value instanceof Store.CloseableResource resource) { if (isAutoCloseEnabled) { - LOGGER.warn( - () -> "Type implements CloseableResource but not AutoCloseable: " + value.getClass().getName()); + store.computeIfAbsent(value.getClass(), type -> { + LOGGER.warn(() -> "Type implements CloseableResource but not AutoCloseable: " + type.getName()); + return true; + }); } resource.close(); } }; } - private static NamespacedHierarchicalStore createStore( - @Nullable ExtensionContext parent, LauncherStoreFacade launcherStoreFacade, - NamespacedHierarchicalStore.CloseAction closeAction) { - NamespacedHierarchicalStore parentStore; - if (parent == null) { - parentStore = launcherStoreFacade.getRequestLevelStore(); - } - else { - parentStore = ((AbstractExtensionContext) parent).valuesStore; - } - return new NamespacedHierarchicalStore<>(parentStore, closeAction); - } - @Override public void close() { this.valuesStore.close(); 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 86bc5ffa08f3..f27808e18b86 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 @@ -71,19 +71,28 @@ void shouldNotCloseAutoCloseableWhenIsClosingStoredAutoCloseablesEnabledIsFalse( void shouldLogWarningWhenResourceImplementsCloseableResourceButNotAutoCloseableAndConfigIsTrue( @TrackLogRecords LogRecordListener listener) throws Exception { ExecutionRecorder executionRecorder = new ExecutionRecorder(); - CloseableResource resource = new CloseableResource(); - String msg = "Type implements CloseableResource but not AutoCloseable: " + resource.getClass().getName(); + CloseableResource resource1 = new CloseableResource(); + CloseableResource resource2 = new CloseableResource(); + CloseableResource resource3 = new CloseableResource() { + }; when(configuration.isClosingStoredAutoCloseablesEnabled()).thenReturn(true); ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionRecorder, testDescriptor, configuration, extensionRegistry, launcherStoreFacade); ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.GLOBAL); - store.put("resource", resource); + store.put("resource1", resource1); + store.put("resource2", resource2); + store.put("resource3", resource3); ((AutoCloseable) extensionContext).close(); - assertThat(listener.stream(Level.WARNING)).map(LogRecord::getMessage).contains(msg); - assertThat(resource.closed).isTrue(); + assertThat(listener.stream(Level.WARNING)).map(LogRecord::getMessage) // + .containsExactlyInAnyOrder( + "Type implements CloseableResource but not AutoCloseable: " + resource1.getClass().getName(), + "Type implements CloseableResource but not AutoCloseable: " + resource3.getClass().getName()); + assertThat(resource1.closed).isTrue(); + assertThat(resource2.closed).isTrue(); + assertThat(resource3.closed).isTrue(); } @Test From 5aed0661acf487dbf4b4ac5c613820654e81dc59 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Sun, 20 Jul 2025 16:45:46 +0200 Subject: [PATCH 113/162] Remove resolved TODO --- .../main/java/org/junit/vintage/engine/VintageTestEngine.java | 1 - 1 file changed, 1 deletion(-) diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java index f5b805705472..7e208cab665e 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java @@ -69,7 +69,6 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId public void execute(ExecutionRequest request) { EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); - // TODO #4725 Provide cancellation support for Vintage engine engineExecutionListener.executionStarted(engineDescriptor); new VintageExecutor(engineDescriptor, engineExecutionListener, request.getConfigurationParameters()).executeAllChildren(request.getCancellationToken()); From b1fa756ec3e64a292901210e2a05462d4daae4cb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 21:25:04 +0000 Subject: [PATCH 114/162] Update plugin spotless to v7.2.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 097c3baefaac..377806277be7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -105,4 +105,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc1" } -spotless = { id = "com.diffplug.spotless", version = "7.1.0" } +spotless = { id = "com.diffplug.spotless", version = "7.2.0" } From fdc11a492cbb006bbd4a37bf6b92dd168677c80b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 21 Jul 2025 08:56:22 +0200 Subject: [PATCH 115/162] Finalize 5.13.4 release notes --- .../release-notes/release-notes-5.13.4.adoc | 39 ++----------------- 1 file changed, 3 insertions(+), 36 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc index d5d027c47759..d36e84b344e6 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -1,9 +1,9 @@ [[release-notes-5.13.4]] == 5.13.4 -*Date of Release:* ❓ +*Date of Release:* July 21, 2025 -*Scope:* ❓ +*Scope:* Bug fixes and enhancements since 5.13.3 For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/101?closed=1+[5.13.4] milestone page in the JUnit @@ -31,30 +31,10 @@ repository on GitHub. * `PackageSource.from(String)` now allows to be constructed with an empty string to indicate the default package. -[[release-notes-5.13.4-junit-platform-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.13.4-junit-platform-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ - [[release-notes-5.13.4-junit-jupiter]] === JUnit Jupiter -[[release-notes-5.13.4-junit-jupiter-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.13.4-junit-jupiter-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - [[release-notes-5.13.4-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements @@ -65,17 +45,4 @@ repository on GitHub. [[release-notes-5.13.4-junit-vintage]] === JUnit Vintage -[[release-notes-5.13.4-junit-vintage-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.13.4-junit-vintage-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.13.4-junit-vintage-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ +No changes. From 1772e3ee8e391d85158399e7caef7d86a8050c8e Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 21 Jul 2025 08:57:36 +0200 Subject: [PATCH 116/162] Release 5.13.4 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a78344e3e773..11f5801c2c73 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This repository is the home of JUnit Platform, Jupiter, and Vintage. ## Latest Releases -- General Availability (GA): [JUnit 5.13.3](https://github.com/junit-team/junit-framework/releases/tag/r5.13.3) (July 4, 2025) +- General Availability (GA): [JUnit 5.13.4](https://github.com/junit-team/junit-framework/releases/tag/r5.13.4) (July 21, 2025) - Preview (Milestone/Release Candidate): [JUnit 6.0.0-M1](https://github.com/junit-team/junit-framework/releases/tag/r6.0.0-M1) (June 27, 2025) ## Documentation From 76f97e97cf3c5cbd7631b9cd1c89f942a8944ab2 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 21 Jul 2025 16:05:59 +0200 Subject: [PATCH 117/162] Remove no longer needed use of reflection to create ForkJoinPool --- ...inPoolHierarchicalTestExecutorService.java | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) 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 f1e08f97816e..c2539d1c2938 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 @@ -17,27 +17,19 @@ import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import java.io.Serial; -import java.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.Constructor; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; -import java.util.Optional; -import java.util.concurrent.Callable; import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.function.Function; -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; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.ConfigurationParameters; @@ -97,33 +89,14 @@ private static ParallelExecutionConfiguration createConfiguration(ConfigurationP } private ForkJoinPool createForkJoinPool(ParallelExecutionConfiguration configuration) { - ForkJoinWorkerThreadFactory threadFactory = new WorkerThreadFactory(); - // Try to use constructor available in Java >= 9 - Callable constructorInvocation = sinceJava9Constructor() // - .map(sinceJava9ConstructorInvocation(configuration, threadFactory)) - // Fallback for Java 8 - .orElse(sinceJava7ConstructorInvocation(configuration, threadFactory)); - return Try.call(constructorInvocation) // - .getOrThrow(cause -> new JUnitException("Failed to create ForkJoinPool", cause)); - } - - private static Optional> sinceJava9Constructor() { - return Try.call(() -> ForkJoinPool.class.getDeclaredConstructor(int.class, ForkJoinWorkerThreadFactory.class, - UncaughtExceptionHandler.class, boolean.class, int.class, int.class, int.class, Predicate.class, long.class, - TimeUnit.class)) // - .toOptional(); - } - - private static Function, Callable> sinceJava9ConstructorInvocation( - ParallelExecutionConfiguration configuration, ForkJoinWorkerThreadFactory threadFactory) { - return constructor -> () -> constructor.newInstance(configuration.getParallelism(), threadFactory, null, false, - configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getMinimumRunnable(), - configuration.getSaturatePredicate(), configuration.getKeepAliveSeconds(), TimeUnit.SECONDS); - } - - private static Callable sinceJava7ConstructorInvocation(ParallelExecutionConfiguration configuration, - ForkJoinWorkerThreadFactory threadFactory) { - return () -> new ForkJoinPool(configuration.getParallelism(), threadFactory, null, false); + try { + return new ForkJoinPool(configuration.getParallelism(), new WorkerThreadFactory(), null, false, + configuration.getCorePoolSize(), configuration.getMaxPoolSize(), configuration.getMinimumRunnable(), + configuration.getSaturatePredicate(), configuration.getKeepAliveSeconds(), TimeUnit.SECONDS); + } + catch (Exception cause) { + throw new JUnitException("Failed to create ForkJoinPool", cause); + } } @Override From 95656d277f2457406e13919fefa5e5d478aac8ba Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 21 Jul 2025 17:53:13 +0200 Subject: [PATCH 118/162] Fail for invalid enum constants supplied as configuration parameters (#4781) * Fail for invalid enum constants supplied as configuration parameters * Document case insensitivity of config params Resolves #4617. --- .../release-notes/release-notes-6.0.0-M2.adoc | 13 +++++ .../java/org/junit/jupiter/api/Timeout.java | 27 +++++---- .../config/DefaultJupiterConfiguration.java | 12 ++-- .../EnumConfigurationParameterConverter.java | 49 +++++++--------- .../extension/TimeoutConfiguration.java | 58 +++++++++++++------ .../engine/extension/TimeoutExtension.java | 57 ++++++------------ .../core/DiscoveryIssueCollector.java | 22 ++++--- .../core/EngineDiscoveryOrchestrator.java | 4 +- .../core/EngineExecutionOrchestrator.java | 4 +- .../platform/launcher/core/LauncherPhase.java | 12 ++-- .../DefaultJupiterConfigurationTests.java | 18 ++++-- .../descriptor/ExtensionContextTests.java | 28 ++------- .../extension/TimeoutConfigurationTests.java | 53 ++++++++++++++--- .../launcher/core/DefaultLauncherTests.java | 32 +++++----- 14 files changed, 215 insertions(+), 174 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index ba7b708eea7d..7b4acc8d8596 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -31,6 +31,10 @@ repository on GitHub. format used by the deprecated `Locale(String)` constructor. * Deprecate `getOrComputeIfAbsent(...)` methods in `NamespacedHierarchicalStore` in favor of the new `computeIfAbsent(...)` methods. +* Setting an invalid value for one of the following enum-based configuration parameters + now causes test discovery to fail: + - `junit.platform.discovery.issue.failure.phase` + - `junit.platform.discovery.issue.severity.critical` [[release-notes-6.0.0-M2-junit-platform-new-features-and-improvements]] ==== New Features and Improvements @@ -78,6 +82,15 @@ repository on GitHub. method. * Deprecate `getOrComputeIfAbsent(...)` methods in `ExtensionContext.Store` in favor of the new `computeIfAbsent(...)` methods. +* Setting an invalid value for one of the following enum-based configuration parameters + now causes test discovery or execution to fail: + - `junit.jupiter.execution.parallel.mode.default` + - `junit.jupiter.execution.parallel.mode.classes.default` + - `junit.jupiter.execution.timeout.mode` + - `junit.jupiter.execution.timeout.thread.mode.default` + - `junit.jupiter.extensions.testinstantiation.extensioncontextscope.default` + - `junit.jupiter.tempdir.cleanup.mode.default` + - `junit.jupiter.testinstance.lifecycle.default` [[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java index f8338fe9a26e..f9f4c39b3c8f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -298,19 +298,20 @@ String DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME = "junit.jupiter.execution.timeout.afterall.method.default"; /** - * Property name used to configure whether timeouts are applied to tests: {@value}. + * Property name used to configure whether timeouts are applied to tests: + * {@value}. * *

    The value of this property will be used to toggle whether * {@link Timeout @Timeout} is applied to tests.

    * - *

    Supported timeout mode values:

    + *

    Supported timeout mode values (case insensitive):

    *
      - *
    • {@code enabled}: enables timeouts - *
    • {@code disabled}: disables timeouts - *
    • {@code disabled_on_debug}: disables timeouts while debugging + *
    • {@code ENABLED}: enables timeouts + *
    • {@code DISABLED}: disables timeouts + *
    • {@code DISABLED_ON_DEBUG}: disables timeouts while debugging *
    * - *

    If not specified, the default is {@code enabled}. + *

    If not specified, the default is {@code ENABLED}. * * @since 5.6 */ @@ -318,16 +319,19 @@ String TIMEOUT_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.mode"; /** - * Property name used to set the default thread mode for all testable and lifecycle - * methods: "junit.jupiter.execution.timeout.thread.mode.default". + * Property name used to set the default thread mode for all testable and + * lifecycle methods: {@value}. * - *

    The value of this property will be used unless overridden by a {@link Timeout @Timeout} - * annotation present on the method or on an enclosing test class (for testable methods). + *

    The value of this property will be used unless overridden by a + * {@link Timeout @Timeout} annotation present on the method or on an + * enclosing test class (for testable methods). * - *

    The supported values are {@code SAME_THREAD} or {@code SEPARATE_THREAD}, if none is provided + *

    The supported values are {@code SAME_THREAD} or + * {@code SEPARATE_THREAD}, ignoring case. If none is provided, * {@code SAME_THREAD} is used as default. * * @since 5.9 + * @see #threadMode() */ @API(status = MAINTAINED, since = "5.13.3") String DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME = "junit.jupiter.execution.timeout.thread.mode.default"; @@ -353,6 +357,7 @@ * @return thread mode * @since 5.9 * @see ThreadMode + * @see #DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME */ @API(status = STABLE, since = "5.11") ThreadMode threadMode() default ThreadMode.INFERRED; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 78bd4e5705e4..081095399db0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -149,19 +149,19 @@ public boolean isThreadDumpOnTimeoutEnabled() { @Override public ExecutionMode getDefaultExecutionMode() { - return executionModeConverter.get(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, + return executionModeConverter.getOrDefault(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, ExecutionMode.SAME_THREAD); } @Override public ExecutionMode getDefaultClassesExecutionMode() { - return executionModeConverter.get(configurationParameters, DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, - getDefaultExecutionMode()); + return executionModeConverter.getOrDefault(configurationParameters, + DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, getDefaultExecutionMode()); } @Override public Lifecycle getDefaultTestInstanceLifecycle() { - return lifecycleConverter.get(configurationParameters, DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, + return lifecycleConverter.getOrDefault(configurationParameters, DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, Lifecycle.PER_METHOD); } @@ -189,7 +189,7 @@ public Optional getDefaultTestClassOrderer() { @Override public CleanupMode getDefaultTempDirCleanupMode() { - return cleanupModeConverter.get(configurationParameters, DEFAULT_CLEANUP_MODE_PROPERTY_NAME, ALWAYS); + return cleanupModeConverter.getOrDefault(configurationParameters, DEFAULT_CLEANUP_MODE_PROPERTY_NAME, ALWAYS); } @Override @@ -202,7 +202,7 @@ public Supplier getDefaultTempDirFactorySupplier() { @SuppressWarnings("deprecation") @Override public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { - return extensionContextScopeConverter.get(configurationParameters, + return extensionContextScopeConverter.getOrDefault(configurationParameters, DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, ExtensionContextScope.DEFAULT); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java index 4d358ceff2f5..4121adecedbb 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java @@ -14,12 +14,12 @@ import java.util.Locale; import java.util.Optional; -import java.util.function.Function; import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; /** @@ -38,36 +38,29 @@ public EnumConfigurationParameterConverter(Class enumType, String enumDisplay this.enumDisplayName = enumDisplayName; } - E get(ConfigurationParameters configParams, String key, E defaultValue) { - Preconditions.notNull(configParams, "ConfigurationParameters must not be null"); - - return get(key, configParams::get, defaultValue); + public Optional get(ExtensionContext extensionContext, String key) { + return extensionContext.getConfigurationParameter(key, value -> convert(key, value)); } - public E get(String key, Function> lookup, E defaultValue) { - - Optional value = lookup.apply(key); + E getOrDefault(ConfigurationParameters configParams, String key, E defaultValue) { + return configParams.get(key) // + .map(value -> convert(key, value)) // + .orElse(defaultValue); + } - if (value.isPresent()) { - String constantName = null; - try { - constantName = value.get().strip().toUpperCase(Locale.ROOT); - E result = Enum.valueOf(enumType, constantName); - logger.config(() -> "Using %s '%s' set via the '%s' configuration parameter.".formatted(enumDisplayName, - result, key)); - return result; - } - catch (Exception ex) { - // local copy necessary for use in lambda expression - String constant = constantName; - logger.warn(() -> """ - Invalid %s '%s' set via the '%s' configuration parameter. \ - Falling back to the %s default value.""".formatted(enumDisplayName, constant, key, - defaultValue.name())); - } + private E convert(String key, String value) { + String constantName = null; + try { + constantName = value.strip().toUpperCase(Locale.ROOT); + E result = Enum.valueOf(enumType, constantName); + logger.config(() -> "Using %s '%s' set via the '%s' configuration parameter.".formatted(enumDisplayName, + result, key)); + return result; + } + catch (Exception ex) { + throw new JUnitException("Invalid %s '%s' set via the '%s' configuration parameter.".formatted( + enumDisplayName, constantName, key)); } - - return defaultValue; } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java index cce55eb24638..c05d1a6715f6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java @@ -21,10 +21,8 @@ import static org.junit.jupiter.api.Timeout.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; -import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; -import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; +import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -33,8 +31,10 @@ import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.config.EnumConfigurationParameterConverter; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.RuntimeUtils; /** * @since 5.5 @@ -47,9 +47,18 @@ class TimeoutConfiguration { private final Map> cache = new ConcurrentHashMap<>(); private final AtomicReference> threadMode = new AtomicReference<>(); private final ExtensionContext extensionContext; + private final boolean timeoutDisabled; TimeoutConfiguration(ExtensionContext extensionContext) { this.extensionContext = extensionContext; + this.timeoutDisabled = new EnumConfigurationParameterConverter<>(TimeoutMode.class, "timeout mode") // + .get(extensionContext, TIMEOUT_MODE_PROPERTY_NAME) // + .map(TimeoutMode::isTimeoutDisabled) // + .orElse(false); + } + + boolean isTimeoutDisabled() { + return timeoutDisabled; } Optional getDefaultTestMethodTimeout() { @@ -125,23 +134,34 @@ Optional getDefaultTimeoutThreadMode() { } private Optional parseTimeoutThreadModeConfiguration() { - return extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME).map(value -> { - try { - ThreadMode threadMode = ThreadMode.valueOf(value.toUpperCase(Locale.ROOT)); - if (threadMode == ThreadMode.INFERRED) { - logger.warn( - () -> "Invalid timeout thread mode '%s', only %s and %s can be used as configuration parameter for %s.".formatted( - value, SAME_THREAD, SEPARATE_THREAD, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)); - return null; - } - return threadMode; + return new EnumConfigurationParameterConverter<>(ThreadMode.class, "timeout thread mode") // + .get(extensionContext, DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME); + } + + private enum TimeoutMode { + + ENABLED { + @Override + boolean isTimeoutDisabled() { + return false; } - catch (Exception e) { - logger.warn(e, - () -> "Invalid timeout thread mode '%s' set via the '%s' configuration parameter.".formatted(value, - DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)); - return null; + }, + + DISABLED { + @Override + boolean isTimeoutDisabled() { + return true; } - }); + }, + + DISABLED_ON_DEBUG { + @Override + boolean isTimeoutDisabled() { + return RuntimeUtils.isDebugMode(); + } + }; + + abstract boolean isTimeoutDisabled(); } + } 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 7d50cc928a1d..3df0137a4c49 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,7 +10,6 @@ package org.junit.jupiter.engine.extension; -import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; import java.lang.reflect.AnnotatedElement; @@ -23,7 +22,6 @@ import org.junit.jupiter.api.Timeout.ThreadMode; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; @@ -31,7 +29,6 @@ import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.RuntimeUtils; /** * @since 5.5 @@ -42,9 +39,6 @@ class TimeoutExtension implements BeforeAllCallback, BeforeEachCallback, Invocat private static final String TESTABLE_METHOD_TIMEOUT_KEY = "testable_method_timeout_from_annotation"; private static final String TESTABLE_METHOD_TIMEOUT_THREAD_MODE_KEY = "testable_method_timeout_thread_mode_from_annotation"; private static final String GLOBAL_TIMEOUT_CONFIG_KEY = "global_timeout_config"; - private static final String ENABLED_MODE_VALUE = "enabled"; - private static final String DISABLED_MODE_VALUE = "disabled"; - private static final String DISABLED_ON_DEBUG_MODE_VALUE = "disabled_on_debug"; @Override public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { @@ -158,41 +152,48 @@ private Optional readTimeoutThreadModeFromAnnotation(Optional invocationContext, ExtensionContext extensionContext, @Nullable TimeoutDuration explicitTimeout, TimeoutProvider defaultTimeoutProvider) throws Throwable { - TimeoutDuration timeout = explicitTimeout == null ? getDefaultTimeout(extensionContext, defaultTimeoutProvider) + TimeoutConfiguration timeoutConfiguration = getGlobalTimeoutConfiguration(extensionContext); + if (timeoutConfiguration.isTimeoutDisabled()) { + return invocation.proceed(); + } + + TimeoutDuration timeout = explicitTimeout == null + ? getDefaultTimeout(defaultTimeoutProvider, timeoutConfiguration) : explicitTimeout; - return decorate(invocation, invocationContext, extensionContext, timeout).proceed(); + return decorate(invocation, invocationContext, extensionContext, timeout, timeoutConfiguration).proceed(); } - private @Nullable TimeoutDuration getDefaultTimeout(ExtensionContext extensionContext, - TimeoutProvider defaultTimeoutProvider) { + private @Nullable TimeoutDuration getDefaultTimeout(TimeoutProvider defaultTimeoutProvider, + TimeoutConfiguration timeoutConfiguration) { - return defaultTimeoutProvider.apply(getGlobalTimeoutConfiguration(extensionContext)).orElse(null); + return defaultTimeoutProvider.apply(timeoutConfiguration).orElse(null); } private TimeoutConfiguration getGlobalTimeoutConfiguration(ExtensionContext extensionContext) { ExtensionContext root = extensionContext.getRoot(); - return root.getStore(NAMESPACE).computeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, - key -> new TimeoutConfiguration(root), TimeoutConfiguration.class); + return root.getStore(NAMESPACE).computeIfAbsent(GLOBAL_TIMEOUT_CONFIG_KEY, __ -> new TimeoutConfiguration(root), + TimeoutConfiguration.class); } private Invocation decorate(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext, - @Nullable TimeoutDuration timeout) { + @Nullable TimeoutDuration timeout, TimeoutConfiguration timeoutConfiguration) { - if (timeout == null || isTimeoutDisabled(extensionContext)) { + if (timeout == null) { return invocation; } - ThreadMode threadMode = resolveTimeoutThreadMode(extensionContext); + ThreadMode threadMode = resolveTimeoutThreadMode(extensionContext, timeoutConfiguration); return new TimeoutInvocationFactory(extensionContext.getRoot().getStore(NAMESPACE)).create(threadMode, new TimeoutInvocationParameters<>(invocation, timeout, () -> describe(invocationContext, extensionContext), PreInterruptCallbackInvocationFactory.create((ExtensionContextInternal) extensionContext))); } - private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext) { + private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext, + TimeoutConfiguration timeoutConfiguration) { ThreadMode annotationThreadMode = getAnnotationThreadMode(extensionContext); if (annotationThreadMode == null || annotationThreadMode == ThreadMode.INFERRED) { - return getGlobalTimeoutConfiguration(extensionContext).getDefaultTimeoutThreadMode().orElse(SAME_THREAD); + return timeoutConfiguration.getDefaultTimeoutThreadMode().orElse(SAME_THREAD); } return annotationThreadMode; } @@ -210,26 +211,6 @@ private String describe(ReflectiveInvocationContext invocationContext, E return ReflectionUtils.getFullyQualifiedMethodName(invocationContext.getTargetClass(), method); } - /** - * Determine if timeouts are disabled for the supplied extension context. - */ - private boolean isTimeoutDisabled(ExtensionContext extensionContext) { - Optional mode = extensionContext.getConfigurationParameter(TIMEOUT_MODE_PROPERTY_NAME); - return mode.map(this::isTimeoutDisabled).orElse(false); - } - - /** - * Determine if timeouts are disabled for the supplied mode. - */ - private boolean isTimeoutDisabled(String mode) { - return switch (mode) { - case ENABLED_MODE_VALUE -> false; - case DISABLED_MODE_VALUE -> true; - case DISABLED_ON_DEBUG_MODE_VALUE -> RuntimeUtils.isDebugMode(); - default -> throw new ExtensionConfigurationException("Unsupported timeout mode: " + mode); - }; - } - @FunctionalInterface private interface TimeoutProvider extends Function> { } 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 e6d8a9ce7c43..70853efe6d1f 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 @@ -19,6 +19,7 @@ import java.util.Locale; 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; import org.junit.platform.engine.ConfigurationParameters; @@ -52,10 +53,10 @@ class DiscoveryIssueCollector implements LauncherDiscoveryListener { private static final Logger logger = LoggerFactory.getLogger(DiscoveryIssueCollector.class); final List issues = new ArrayList<>(); - private final ConfigurationParameters configurationParameters; + private final Severity criticalSeverity; DiscoveryIssueCollector(ConfigurationParameters configurationParameters) { - this.configurationParameters = configurationParameters; + this.criticalSeverity = getCriticalSeverity(configurationParameters); } @Override @@ -136,24 +137,21 @@ DiscoveryIssueNotifier toNotifier() { if (this.issues.isEmpty()) { return DiscoveryIssueNotifier.NO_ISSUES; } - return DiscoveryIssueNotifier.from(getCriticalSeverity(), this.issues); + return DiscoveryIssueNotifier.from(criticalSeverity, this.issues); } - private Severity getCriticalSeverity() { - Severity defaultValue = Severity.ERROR; - return this.configurationParameters // + private static Severity getCriticalSeverity(ConfigurationParameters configurationParameters) { + return configurationParameters // .get(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, value -> { try { return Severity.valueOf(value.toUpperCase(Locale.ROOT)); } catch (Exception e) { - logger.warn(() -> """ - Invalid DiscoveryIssue.Severity '%s' set via the '%s' configuration parameter. \ - Falling back to the %s default value.""".formatted(value, - LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, defaultValue)); - return defaultValue; + throw new JUnitException( + "Invalid DiscoveryIssue.Severity '%s' set via the '%s' configuration parameter.".formatted( + value, LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME)); } }) // - .orElse(defaultValue); + .orElse(Severity.ERROR); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 5c4a2f779ae9..c4910536b993 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -133,8 +133,8 @@ public LauncherDiscoveryListener getDiscoveryListener() { private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryRequest request, Optional phase) { ConfigurationParameters configurationParameters = request.getConfigurationParameters(); - return getDiscoveryIssueFailurePhase(configurationParameters).orElse( - phase.orElse(null)) == LauncherPhase.DISCOVERY; + return getDiscoveryIssueFailurePhase(configurationParameters) // + .orElse(phase.orElse(null)) == LauncherPhase.DISCOVERY; } private static void reportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index 848dd8931211..0e0e213df14e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -223,8 +223,8 @@ else if (cancellationToken.isCancellationRequested()) { private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { ConfigurationParameters configurationParameters = discoveryResult.getConfigurationParameters(); - return getDiscoveryIssueFailurePhase(configurationParameters).orElse( - LauncherPhase.EXECUTION) == LauncherPhase.EXECUTION; + return getDiscoveryIssueFailurePhase(configurationParameters) // + .orElse(LauncherPhase.EXECUTION) == LauncherPhase.EXECUTION; } private ListenerRegistry buildListenerRegistryForExecution( diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java index 33d612d7a26e..ab82576fbdd1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java @@ -17,8 +17,7 @@ import java.util.function.Function; import org.jspecify.annotations.Nullable; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.JUnitException; import org.junit.platform.engine.ConfigurationParameters; /** @@ -30,18 +29,15 @@ enum LauncherPhase { DISCOVERY, EXECUTION; - private static final Logger logger = LoggerFactory.getLogger(LauncherPhase.class); - static Optional getDiscoveryIssueFailurePhase(ConfigurationParameters configurationParameters) { Function stringLauncherPhaseFunction = value -> { try { return LauncherPhase.valueOf(value.toUpperCase(Locale.ROOT)); } catch (Exception e) { - logger.warn( - () -> "Ignoring invalid LauncherPhase '%s' set via the '%s' configuration parameter.".formatted( - value, DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME)); - return null; + throw new JUnitException( + "Invalid LauncherPhase '%s' set via the '%s' configuration parameter.".formatted(value, + DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME)); } }; return configurationParameters.get(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, stringLauncherPhaseFunction); 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 af5fc8088059..37b134b978de 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 @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.config; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -71,8 +72,13 @@ void getDefaultTempDirCleanupModeWithNoConfigParamSet() { void getDefaultTestInstanceLifecycleWithConfigParamSet() { assertAll(// () -> assertDefaultConfigParam(null, PER_METHOD), // - () -> assertDefaultConfigParam("", PER_METHOD), // - () -> assertDefaultConfigParam("bogus", PER_METHOD), // + () -> assertThatThrownBy(() -> getDefaultTestInstanceLifecycleConfigParam("")) // + .hasMessage("Invalid test instance lifecycle mode '' set via the '%s' configuration parameter.", + DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME), // + () -> assertThatThrownBy(() -> getDefaultTestInstanceLifecycleConfigParam("bogus")) // + .hasMessage( + "Invalid test instance lifecycle mode 'BOGUS' set via the '%s' configuration parameter.", + DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME), // () -> assertDefaultConfigParam(PER_METHOD.name(), PER_METHOD), // () -> assertDefaultConfigParam(PER_METHOD.name().toLowerCase(), PER_METHOD), // () -> assertDefaultConfigParam(" " + PER_METHOD.name() + " ", PER_METHOD), // @@ -156,11 +162,15 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { } private void assertDefaultConfigParam(@Nullable String configValue, Lifecycle expected) { + var lifecycle = getDefaultTestInstanceLifecycleConfigParam(configValue); + assertThat(lifecycle).isEqualTo(expected); + } + + private static Lifecycle getDefaultTestInstanceLifecycleConfigParam(@Nullable String configValue) { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue)); - Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider(), + return new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider(), mock()).getDefaultTestInstanceLifecycle(); - assertThat(lifecycle).isEqualTo(expected); } } 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 61993435a692..186623bf82dc 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 @@ -34,7 +34,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import org.jspecify.annotations.Nullable; @@ -454,11 +453,15 @@ void usingStore() { @ParameterizedTest @MethodSource("extensionContextFactories") void configurationParameter(Function extensionContextFactory) { - JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters(), - dummyOutputDirectoryProvider(), mock()); + var key = "123"; var expected = Optional.of(key); + ConfigurationParameters configurationParameters = mock(); + when(configurationParameters.get("123")).thenReturn(expected); + JupiterConfiguration echo = new DefaultJupiterConfiguration(configurationParameters, + dummyOutputDirectoryProvider(), mock()); + var context = extensionContextFactory.apply(echo); assertEquals(expected, context.getConfigurationParameter(key)); @@ -555,23 +558,4 @@ void aMethod() { } } - private static class EchoParameters implements ConfigurationParameters { - - @Override - public Optional get(String key) { - return Optional.of(key); - } - - @Override - public Optional getBoolean(String key) { - throw new UnsupportedOperationException("getBoolean(String) should not be called"); - } - - @Override - public Set keySet() { - throw new UnsupportedOperationException("keySet() should not be called"); - - } - } - } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java index ffda80ba26a3..b2e2980fd0f7 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java @@ -18,6 +18,8 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Timeout.TIMEOUT_MODE_PROPERTY_NAME; import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME; @@ -30,17 +32,24 @@ import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.DEFAULT_TIMEOUT_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Optional; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.LogRecord; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.util.RuntimeUtils; +import org.mockito.stubbing.Answer; /** * @since 5.5 @@ -50,6 +59,27 @@ class TimeoutConfigurationTests { ExtensionContext extensionContext = mock(); TimeoutConfiguration config = new TimeoutConfiguration(extensionContext); + @Test + void notDisabledByDefault() { + assertThat(config.isTimeoutDisabled()).isFalse(); + } + + @ParameterizedTest + @CsvSource(textBlock = """ + Enabled, false + Disabled, true + Disabled_ON_debug, false + """) + void disabledBasedOnTimeoutMode(String timeoutMode, boolean disabled) { + when(extensionContext.getConfigurationParameter(eq(TIMEOUT_MODE_PROPERTY_NAME), any())) // + .thenAnswer(callConverter(timeoutMode)); + + config = new TimeoutConfiguration(extensionContext); + + assertThat(config.isTimeoutDisabled()) // + .isEqualTo("disabled_on_debug".equalsIgnoreCase(timeoutMode) ? RuntimeUtils.isDebugMode() : disabled); + } + @Test void noTimeoutIfNoPropertiesAreSet() { assertThat(config.getDefaultBeforeAllMethodTimeout()).isEmpty(); @@ -136,19 +166,24 @@ void logsInvalidValues(@TrackLogRecords LogRecordListener logRecordListener) { @Test void specificThreadModeIsUsed() { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( - Optional.of("SEPARATE_THREAD")); + when(extensionContext.getConfigurationParameter(eq(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME), any())) // + .thenAnswer(callConverter("SEPARATE_THREAD")); + assertThat(config.getDefaultTimeoutThreadMode()).contains(SEPARATE_THREAD); } @Test - void logsInvalidThreadModeValueAndReturnEmpty(@TrackLogRecords LogRecordListener logRecordListener) { - when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( - Optional.of("invalid")); + void logsInvalidThreadModeValueAndReturnEmpty() { + when(extensionContext.getConfigurationParameter(eq(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME), any())) // + .thenAnswer(callConverter("invalid")); - assertThat(config.getDefaultTimeoutThreadMode()).isNotPresent(); - assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // - .containsExactly( - "Invalid timeout thread mode 'invalid' set via the 'junit.jupiter.execution.timeout.thread.mode.default' configuration parameter."); + assertThatThrownBy(() -> config.getDefaultTimeoutThreadMode()) // + .hasMessage( + "Invalid timeout thread mode 'INVALID' set via the 'junit.jupiter.execution.timeout.thread.mode.default' configuration parameter."); + } + + @SuppressWarnings("unchecked") + private static Answer callConverter(String value) { + return invocation -> Optional.ofNullable(invocation.getArgument(1, Function.class).apply(value)); } } 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 33bb887bd4e2..bc3ede9085d5 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 @@ -905,8 +905,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId } @Test - void fallsBackToErrorSeverityIfCriticalSeverityIsConfiguredIncorrectly( - @TrackLogRecords LogRecordListener listener) { + void failsIfCriticalSeverityIsConfiguredIncorrectly() { var engine = new TestEngineStub("engine-id") { @Override @@ -925,18 +924,12 @@ public void execute(ExecutionRequest request) { } }; - var result = execute(engine, request -> request // - .configurationParameter(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, "wrong")); - - assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.SUCCESSFUL); + var exception = assertThrows(JUnitException.class, () -> execute(engine, request -> request // + .configurationParameter(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, "wrong"))); - var logRecord = listener.stream(DiscoveryIssueCollector.class, Level.WARNING) // - .findFirst() // - .orElseThrow(); - assertThat(logRecord.getMessage()) // - .isEqualTo( - "Invalid DiscoveryIssue.Severity 'wrong' set via the '%s' configuration parameter. " - + "Falling back to the ERROR default value.", + assertThat(exception) // + .hasRootCauseMessage( + "Invalid DiscoveryIssue.Severity 'wrong' set via the '%s' configuration parameter.", LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME); } @@ -989,6 +982,19 @@ public void execute(ExecutionRequest request) { assertThat(listener.stream(DiscoveryIssueNotifier.class, Level.WARNING)).hasSize(1); } + @Test + void failsDuringDiscoveryIfConfigurationParameterValueIsInvalid() { + + var engine = new TestEngineStub("engine-id"); + + var exception = assertThrows(JUnitException.class, () -> execute(engine, request -> request // + .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "wrong"))); + + assertThat(exception) // + .hasRootCauseMessage("Invalid LauncherPhase 'wrong' set via the '%s' configuration parameter.", + LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME); + } + @Test void reportsChildrenOfEngineDescriptorAsSkippedAfterCancellationWasRequested() { var engine = spy(new TestEngineStub("engine-id") { From 9a036cef9e7f28b05348f43421f158c06ccaf3da Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:54:11 +0000 Subject: [PATCH 119/162] Update github/codeql-action action to v3.29.3 --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 604c2ac2ff69..d3fa15ef67de 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/init@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -53,6 +53,6 @@ jobs: -Dscan.tag.CodeQL \ classes - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/analyze@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 59734595a725..c60b0ba136b5 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: sarif_file: results.sarif From 8770829ac308de61098aa1664ed8521aaf7614a5 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 21 Jul 2025 20:26:36 +0300 Subject: [PATCH 120/162] Consistently use "name = value" arguments format in parameterized tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, name-value pairs for arguments in display names for parameterized classes and tests had different formatting/spacing depending on the use case. When using the `useHeadersInDisplayName` feature of @⁠CsvSource or @⁠CsvFileSource, they took the form "name = value"; whereas, when using the {argumentsWithNames} display name pattern, they took the form "name=value" (without spaces). With this commit, we now consistently use the "name = value" formatting (with spaces). Closes #4786 --- .../release-notes/release-notes-6.0.0-M2.adoc | 3 + .../asciidoc/user-guide/writing-tests.adoc | 48 ++--- .../ParameterizedInvocationNameFormatter.java | 5 +- .../ParameterizedClassIntegrationTests.java | 96 ++++----- ...meterizedInvocationNameFormatterTests.java | 6 +- .../ParameterizedTestIntegrationTests.java | 187 +++++++++--------- ...InvocationNameFormatterIntegrationTests.kt | 4 +- .../params/aggregator/DisplayNameTests.kt | 2 +- 8 files changed, 183 insertions(+), 168 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index 7b4acc8d8596..acdeab41d797 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -95,6 +95,9 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements +* Display names for `@ParameterizedClass` and `@ParameterizedTest` now consistently style + name-value pairs for arguments using `name = value` formatting – for example, + `fruit = apple` instead of `fruit=apple`. * Reason strings supplied to `ConditionEvaluationResult` APIs are now officially declared as `@Nullable`. * Introduce `computeIfAbsent(...)` methods in `ExtensionContext.Store` to ease simplify diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 6b26c7b2b86d..68aaf52731b6 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1242,26 +1242,26 @@ Executing the above test class yields the following output: .... FruitTests ✔ -├─ [1] fruit=apple ✔ +├─ [1] fruit = apple ✔ │ └─ QuantityTests ✔ -│ ├─ [1] quantity=23 ✔ +│ ├─ [1] quantity = 23 ✔ │ │ └─ test(Duration) ✔ -│ │ ├─ [1] duration=PT1H ✔ -│ │ └─ [2] duration=PT2H ✔ -│ └─ [2] quantity=42 ✔ +│ │ ├─ [1] duration = PT1H ✔ +│ │ └─ [2] duration = PT2H ✔ +│ └─ [2] quantity = 42 ✔ │ └─ test(Duration) ✔ -│ ├─ [1] duration=PT1H ✔ -│ └─ [2] duration=PT2H ✔ -└─ [2] fruit=banana ✔ +│ ├─ [1] duration = PT1H ✔ +│ └─ [2] duration = PT2H ✔ +└─ [2] fruit = banana ✔ └─ QuantityTests ✔ - ├─ [1] quantity=23 ✔ + ├─ [1] quantity = 23 ✔ │ └─ test(Duration) ✔ - │ ├─ [1] duration=PT1H ✔ - │ └─ [2] duration=PT2H ✔ - └─ [2] quantity=42 ✔ + │ ├─ [1] duration = PT1H ✔ + │ └─ [2] duration = PT2H ✔ + └─ [2] quantity = 42 ✔ └─ test(Duration) ✔ - ├─ [1] duration=PT1H ✔ - └─ [2] duration=PT2H ✔ + ├─ [1] duration = PT1H ✔ + └─ [2] duration = PT2H ✔ .... [[writing-tests-dependency-injection]] @@ -1649,9 +1649,9 @@ following. .... palindromes(String) ✔ -├─ [1] candidate=racecar ✔ -├─ [2] candidate=radar ✔ -└─ [3] candidate=able was I ere I saw elba ✔ +├─ [1] candidate = racecar ✔ +├─ [2] candidate = radar ✔ +└─ [3] candidate = able was I ere I saw elba ✔ .... The same `@ValueSource` annotation can be used to specify the source of arguments for a @@ -1668,13 +1668,13 @@ following. .... PalindromeTests ✔ -├─ [1] candidate=racecar ✔ +├─ [1] candidate = racecar ✔ │ ├─ palindrome() ✔ │ └─ reversePalindrome() ✔ -├─ [2] candidate=radar ✔ +├─ [2] candidate = radar ✔ │ ├─ palindrome() ✔ │ └─ reversePalindrome() ✔ -└─ [3] candidate=able was I ere I saw elba ✔ +└─ [3] candidate = able was I ere I saw elba ✔ ├─ palindrome() ✔ └─ reversePalindrome() ✔ .... @@ -2332,10 +2332,10 @@ The following listing shows the generated display names for the first two parame test methods above. ---- -[1] country=Sweden, reference=1 -[2] country=Poland, reference=2 -[3] country=United States of America, reference=3 -[4] country=France, reference=700_000 +[1] country = Sweden, reference = 1 +[2] country = Poland, reference = 2 +[3] country = United States of America, reference = 3 +[4] country = France, reference = 700_000 ---- The following listing shows the generated display names for the last parameterized test 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 beebae5a8a34..9b5353cb150b 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 @@ -187,8 +187,9 @@ private PartialFormatters createPartialFormatters(String displayName, private static String argumentsWithNamesPattern(int length, ParameterizedDeclarationContext declarationContext) { ResolverFacade resolverFacade = declarationContext.getResolverFacade(); return IntStream.range(0, length) // - .mapToObj(index -> resolverFacade.getParameterName(index).map(name -> name + "=").orElse("") + "{" - + index + "}") // + .mapToObj(index -> resolverFacade.getParameterName(index)// + .map(name -> name + " = ").orElse("") // + + "{" + index + "}") // .collect(joining(", ")); } 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 2e776abf2c69..8a8c76b8f7b4 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 @@ -124,7 +124,7 @@ void injectsParametersIntoClass(Class classTemplateClass) { var results = executeTestsForClass(classTemplateClass); - String parameterNamePrefix = classTemplateClass.getSimpleName().contains("Aggregator") ? "" : "value="; + String parameterNamePrefix = classTemplateClass.getSimpleName().contains("Aggregator") ? "" : "value = "; results.allEvents().assertEventsMatchExactly( // event(engine(), started()), // @@ -176,7 +176,7 @@ void supportsNullAndEmptySource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value=null", "[2] value="); + .containsExactly("[1] value = null", "[2] value = "); } @ParameterizedTest @@ -188,8 +188,8 @@ void supportsCsvFileSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(10).succeeded(10)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] name=foo, value=1", "[2] name=bar, value=2", "[3] name=baz, value=3", - "[4] name=qux, value=4"); + .containsExactly("[1] name = foo, value = 1", "[2] name = bar, value = 2", + "[3] name = baz, value = 3", "[4] name = qux, value = 4"); } @ParameterizedTest @@ -201,7 +201,7 @@ void supportsSingleEnumSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(4).succeeded(4)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value=FOO"); + .containsExactly("[1] value = FOO"); } @ParameterizedTest @@ -213,7 +213,7 @@ void supportsRepeatedEnumSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value=FOO", "[2] value=BAR"); + .containsExactly("[1] value = FOO", "[2] value = BAR"); } @ParameterizedTest @@ -225,7 +225,7 @@ void supportsMethodSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value=foo", "[2] value=bar"); + .containsExactly("[1] value = foo", "[2] value = bar"); } @Test @@ -247,7 +247,7 @@ void supportsFieldSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value=foo", "[2] value=bar"); + .containsExactly("[1] value = foo", "[2] value = bar"); } @Test @@ -269,7 +269,7 @@ void supportsArgumentsSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value=foo", "[2] value=bar"); + .containsExactly("[1] value = foo", "[2] value = bar"); } @Test @@ -305,7 +305,7 @@ void supportsCustomNamePatterns() { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("1 | TesT | 1, foo | set", "2 | TesT | 2, bar | number=2, name=bar"); + .containsExactly("1 | TesT | 1, foo | set", "2 | TesT | 2, bar | number = 2, name = bar"); } @Test @@ -398,8 +398,8 @@ void supportsNestedParameterizedClass(Class classTemplateClass) { results.testEvents().assertStatistics(stats -> stats.started(8).succeeded(8)); assertThat(invocationDisplayNames(results)) // .containsExactly( // - "[1] number=1", "[1] text=foo", "[2] text=bar", // - "[2] number=2", "[1] text=foo", "[2] text=bar" // + "[1] number = 1", "[1] text = foo", "[2] text = bar", // + "[2] number = 2", "[1] text = foo", "[2] text = bar" // ); assertThat(allReportEntries(results)).map(it -> it.get("value")).containsExactly( // @formatter:off @@ -407,56 +407,56 @@ void supportsNestedParameterizedClass(Class classTemplateClass) { "beforeParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "beforeAll: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", - "beforeEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [1] flag=true [InnerTestCase]", + "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [1] flag = true [InnerTestCase]", "test(1, foo, true)", - "afterEach: [1] flag=true [InnerTestCase]", - "afterEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [InnerTestCase]", + "afterEach: [1] flag = true [InnerTestCase]", + "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [InnerTestCase]", "test(1, foo, false)", - "afterEach: [2] flag=false [InnerTestCase]", - "afterEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), + "afterEach: [2] flag = false [InnerTestCase]", + "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", - "beforeEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [1] flag=true [InnerTestCase]", + "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [1] flag = true [InnerTestCase]", "test(1, bar, true)", - "afterEach: [1] flag=true [InnerTestCase]", - "afterEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [InnerTestCase]", + "afterEach: [1] flag = true [InnerTestCase]", + "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [InnerTestCase]", "test(1, bar, false)", - "afterEach: [2] flag=false [InnerTestCase]", - "afterEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), + "afterEach: [2] flag = false [InnerTestCase]", + "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "afterAll: InnerTestCase", "afterParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "beforeParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), "beforeAll: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", - "beforeEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [1] flag=true [InnerTestCase]", + "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [1] flag = true [InnerTestCase]", "test(2, foo, true)", - "afterEach: [1] flag=true [InnerTestCase]", - "afterEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [InnerTestCase]", + "afterEach: [1] flag = true [InnerTestCase]", + "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [InnerTestCase]", "test(2, foo, false)", - "afterEach: [2] flag=false [InnerTestCase]", - "afterEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), + "afterEach: [2] flag = false [InnerTestCase]", + "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "beforeParameterizedClassInvocation: InnerTestCase", - "beforeEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [1] flag=true [InnerTestCase]", + "beforeEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [1] flag = true [InnerTestCase]", "test(2, bar, true)", - "afterEach: [1] flag=true [InnerTestCase]", - "afterEach: [1] flag=true [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), - "beforeEach: [2] flag=false [InnerTestCase]", + "afterEach: [1] flag = true [InnerTestCase]", + "afterEach: [1] flag = true [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), + "beforeEach: [2] flag = false [InnerTestCase]", "test(2, bar, false)", - "afterEach: [2] flag=false [InnerTestCase]", - "afterEach: [2] flag=false [%s]".formatted(classTemplateClass.getSimpleName()), + "afterEach: [2] flag = false [InnerTestCase]", + "afterEach: [2] flag = false [%s]".formatted(classTemplateClass.getSimpleName()), "afterParameterizedClassInvocation: InnerTestCase", "afterAll: InnerTestCase", "afterParameterizedClassInvocation: %s".formatted(classTemplateClass.getSimpleName()), @@ -2090,14 +2090,14 @@ static void before2(ArgumentsAccessor accessor) { @BeforeParameterizedClassInvocation static void before3(AtomicInteger value, TestInfo testInfo) { - assertEquals("[1] value=1", testInfo.getDisplayName()); + assertEquals("[1] value = 1", testInfo.getDisplayName()); value.incrementAndGet(); } @BeforeParameterizedClassInvocation static void before4(ArgumentsAccessor accessor, TestInfo testInfo) { assertEquals(1, accessor.getInteger(0)); - assertEquals("[1] value=1", testInfo.getDisplayName()); + assertEquals("[1] value = 1", testInfo.getDisplayName()); } @BeforeParameterizedClassInvocation @@ -2109,7 +2109,7 @@ static void before4(AtomicInteger value, ArgumentsAccessor accessor) { @BeforeParameterizedClassInvocation static void before5(AtomicInteger value, ArgumentsAccessor accessor, TestInfo testInfo) { assertEquals(1, accessor.getInteger(0)); - assertEquals("[1] value=1", testInfo.getDisplayName()); + assertEquals("[1] value = 1", testInfo.getDisplayName()); value.incrementAndGet(); } @@ -2122,7 +2122,7 @@ static void before6(@TimesTwo int valueTimesTwo) { static void after(AtomicInteger value, ArgumentsAccessor accessor, TestInfo testInfo) { assertEquals(6, value.get()); assertEquals(1, accessor.getInteger(0)); - assertEquals("[1] value=1", testInfo.getDisplayName()); + assertEquals("[1] value = 1", testInfo.getDisplayName()); } } 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 bee72ba81725..13491d975741 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 @@ -140,7 +140,7 @@ void formatsCompleteArgumentsListWithNames() { var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); var formattedName = format(formatter, 1, arguments(42, "enigma", new Object[] { "foo", 1 })); - assertEquals("someNumber=42, someString=enigma, someArray=[foo, 1]", formattedName); + assertEquals("someNumber = 42, someString = enigma, someArray = [foo, 1]", formattedName); } @Test @@ -149,7 +149,7 @@ void formatsCompleteArgumentsListWithoutNamesForAggregators() { var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); var formattedName = format(formatter, 1, arguments(42, "foo", "bar")); - assertEquals("someNumber=42, foo, bar", formattedName); + assertEquals("someNumber = 42, foo, bar", formattedName); } @Test @@ -318,7 +318,7 @@ void mixedTypesOfArgumentsImplementationsAndCustomDisplayNamePattern() { var name2 = format(formatter, 2, arguments("apple", "banana")); assertThat(name1).isEqualTo("[1] Mixed Arguments Types :: Fruits"); - assertThat(name2).isEqualTo("[2] Mixed Arguments Types :: fruit1=apple, fruit2=banana"); + assertThat(name2).isEqualTo("[2] Mixed Arguments Types :: fruit1 = apple, fruit2 = banana"); } } 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 133d7390530b..6f751032d63a 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 @@ -258,16 +258,16 @@ private void assertFruitTable(@Nullable String fruit, double rank, TestInfo test void executesWithSingleArgumentsProviderWithMultipleInvocations() { var results = execute("testWithTwoSingleStringArgumentsProvider", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvSource() { var results = execute("testWithCsvSource", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + .haveExactly(1, event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test @@ -295,8 +295,8 @@ void executesWithMessageFormat() { void executesWithPrimitiveWideningConversion() { var results = execute("testWithPrimitiveWideningConversion", double.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] num=1"), finishedWithFailure(message("num: 1.0")))) // - .haveExactly(1, event(test(), displayName("[2] num=2"), finishedWithFailure(message("num: 2.0")))); + .haveExactly(1, event(test(), displayName("[1] num = 1"), finishedWithFailure(message("num: 1.0")))) // + .haveExactly(1, event(test(), displayName("[2] num = 2"), finishedWithFailure(message("num: 2.0")))); } /** @@ -306,8 +306,9 @@ void executesWithPrimitiveWideningConversion() { void executesWithImplicitGenericConverter() { var results = execute("testWithImplicitGenericConverter", Book.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] book=book 1"), finishedWithFailure(message("book 1")))) // - .haveExactly(1, event(test(), displayName("[2] book=book 2"), finishedWithFailure(message("book 2")))); + .haveExactly(1, event(test(), displayName("[1] book = book 1"), finishedWithFailure(message("book 1")))) // + .haveExactly(1, + event(test(), displayName("[2] book = book 2"), finishedWithFailure(message("book 2")))); } @Test @@ -327,9 +328,9 @@ void legacyReportingNames() { void executesWithExplicitConverter() { var results = execute("testWithExplicitConverter", int.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] length=O"), finishedWithFailure(message("length: 1")))) // + .haveExactly(1, event(test(), displayName("[1] length = O"), finishedWithFailure(message("length: 1")))) // .haveExactly(1, - event(test(), displayName("[2] length=XXX"), finishedWithFailure(message("length: 3")))); + event(test(), displayName("[2] length = XXX"), finishedWithFailure(message("length: 3")))); } @Test @@ -403,9 +404,9 @@ void executesLifecycleMethods() { var results = executeTestsForClass(LifecycleTestCase.class); results.allEvents().assertThatEvents() // .haveExactly(1, - event(test("test1"), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // + event(test("test1"), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test("test1"), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test("test1"), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); List testMethods = new ArrayList<>(LifecycleTestCase.testMethods); @@ -413,23 +414,23 @@ void executesLifecycleMethods() { assertThat(LifecycleTestCase.lifecycleEvents).containsExactly( "beforeAll:ParameterizedTestIntegrationTests$LifecycleTestCase", "providerMethod", - "constructor:[1] argument=foo", - "beforeEach:[1] argument=foo", - testMethods.get(0) + ":[1] argument=foo", - "afterEach:[1] argument=foo", - "constructor:[2] argument=bar", - "beforeEach:[2] argument=bar", - testMethods.get(0) + ":[2] argument=bar", - "afterEach:[2] argument=bar", + "constructor:[1] argument = foo", + "beforeEach:[1] argument = foo", + testMethods.get(0) + ":[1] argument = foo", + "afterEach:[1] argument = foo", + "constructor:[2] argument = bar", + "beforeEach:[2] argument = bar", + testMethods.get(0) + ":[2] argument = bar", + "afterEach:[2] argument = bar", "providerMethod", - "constructor:[1] argument=foo", - "beforeEach:[1] argument=foo", - testMethods.get(1) + ":[1] argument=foo", - "afterEach:[1] argument=foo", - "constructor:[2] argument=bar", - "beforeEach:[2] argument=bar", - testMethods.get(1) + ":[2] argument=bar", - "afterEach:[2] argument=bar", + "constructor:[1] argument = foo", + "beforeEach:[1] argument = foo", + testMethods.get(1) + ":[1] argument = foo", + "afterEach:[1] argument = foo", + "constructor:[2] argument = bar", + "beforeEach:[2] argument = bar", + testMethods.get(1) + ":[2] argument = bar", + "afterEach:[2] argument = bar", "afterAll:ParameterizedTestIntegrationTests$LifecycleTestCase"); // @formatter:on } @@ -441,8 +442,8 @@ void truncatesArgumentsThatExceedMaxLength() { .selectors(selectMethod(TestCase.class, "testWithCsvSource", String.class.getName())) // .execute(); results.testEvents().assertThatEvents() // - .haveExactly(1, event(displayName("[1] argument=f…"), started())) // - .haveExactly(1, event(displayName("[2] argument=b…"), started())); + .haveExactly(1, event(displayName("[1] argument = f…"), started())) // + .haveExactly(1, event(displayName("[2] argument = b…"), started())); } @Test @@ -543,21 +544,21 @@ class NullSourceIntegrationTests { void executesWithNullSourceForString() { var results = execute("testWithNullSourceForString", String.class); results.testEvents().failed().assertEventsMatchExactly( - event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); + event(test(), displayName("[1] argument = null"), finishedWithFailure(message("null")))); } @Test void executesWithNullSourceForStringAndTestInfo() { var results = execute("testWithNullSourceForStringAndTestInfo", String.class, TestInfo.class); results.testEvents().failed().assertEventsMatchExactly( - event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); + event(test(), displayName("[1] argument = null"), finishedWithFailure(message("null")))); } @Test void executesWithNullSourceForNumber() { var results = execute("testWithNullSourceForNumber", Number.class); results.testEvents().failed().assertEventsMatchExactly( - event(test(), displayName("[1] argument=null"), finishedWithFailure(message("null")))); + event(test(), displayName("[1] argument = null"), finishedWithFailure(message("null")))); } @Test @@ -574,7 +575,7 @@ void failsWithNullSourceWithZeroFormalParameters() { @Test void failsWithNullSourceForPrimitive() { var results = execute("testWithNullSourceForPrimitive", int.class); - results.testEvents().failed().assertEventsMatchExactly(event(test(), displayName("[1] argument=null"), + results.testEvents().failed().assertEventsMatchExactly(event(test(), displayName("[1] argument = null"), finishedWithFailure(instanceOf(ParameterResolutionException.class), message( "Error converting parameter at index 0: Cannot convert null to primitive value of type int")))); } @@ -595,13 +596,13 @@ class EmptySourceIntegrationTests { @Test void executesWithEmptySourceForString() { var results = execute("testWithEmptySourceForString", String.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument="))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = "))); } @Test void executesWithEmptySourceForStringAndTestInfo() { var results = execute("testWithEmptySourceForStringAndTestInfo", String.class, TestInfo.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument="))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = "))); } /** @@ -610,13 +611,13 @@ void executesWithEmptySourceForStringAndTestInfo() { @Test void executesWithEmptySourceForCollection() { var results = execute("testWithEmptySourceForCollection", Collection.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForList() { var results = execute("testWithEmptySourceForList", List.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** @@ -629,13 +630,13 @@ void executesWithEmptySourceForList() { """) void executesWithEmptySourceForListSubtype(String methodName, Class parameterType) { var results = execute(methodName, parameterType); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForSet() { var results = execute("testWithEmptySourceForSet", Set.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } /** @@ -651,13 +652,13 @@ void executesWithEmptySourceForSet() { """) void executesWithEmptySourceForSetSubtype(String methodName, Class parameterType) { var results = execute(methodName, parameterType); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForMap() { var results = execute("testWithEmptySourceForMap", Map.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument={}"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = {}"))); } /** @@ -673,31 +674,31 @@ void executesWithEmptySourceForMap() { """) void executesWithEmptySourceForMapSubtype(String methodName, Class parameterType) { var results = execute(methodName, parameterType); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument={}"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = {}"))); } @Test void executesWithEmptySourceForOneDimensionalPrimitiveArray() { var results = execute("testWithEmptySourceForOneDimensionalPrimitiveArray", int[].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForOneDimensionalStringArray() { var results = execute("testWithEmptySourceForOneDimensionalStringArray", String[].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForTwoDimensionalPrimitiveArray() { var results = execute("testWithEmptySourceForTwoDimensionalPrimitiveArray", int[][].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test void executesWithEmptySourceForTwoDimensionalStringArray() { var results = execute("testWithEmptySourceForTwoDimensionalStringArray", String[][].class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument=[]"))); + results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = []"))); } @Test @@ -782,15 +783,15 @@ private EngineExecutionResults execute(String methodName, Class... methodPara private void assertNullAndEmptyString(EngineExecutionResults results) { results.testEvents().succeeded().assertEventsMatchExactly(// - event(test(), displayName("[1] argument=null")), // - event(test(), displayName("[2] argument="))// + event(test(), displayName("[1] argument = null")), // + event(test(), displayName("[2] argument = "))// ); } private void assertNullAndEmpty(EngineExecutionResults results) { results.testEvents().succeeded().assertEventsMatchExactly(// - event(test(), displayName("[1] argument=null")), // - event(test(), displayName("[2] argument=[]"))// + event(test(), displayName("[1] argument = null")), // + event(test(), displayName("[2] argument = []"))// ); } @@ -1118,45 +1119,50 @@ class UnusedArgumentsIntegrationTests { void executesWithArgumentsSourceProvidingUnusedArguments() { var results = execute("testWithTwoUnusedStringArgumentsProvider", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvSourceContainingUnusedArguments() { var results = execute("testWithCsvSourceContainingUnusedArguments", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithCsvFileSourceContainingUnusedArguments() { var results = execute("testWithCsvFileSourceContainingUnusedArguments", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithMethodSourceProvidingUnusedArguments() { var results = execute("testWithMethodSourceProvidingUnusedArguments", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test void executesWithFieldSourceProvidingUnusedArguments() { var results = execute("testWithFieldSourceProvidingUnusedArguments", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { @@ -1203,7 +1209,7 @@ void failsWithCsvSourceUnusedArgumentsButExecutesRemainingArgumentsWhereThereIsN .haveExactly(1, event(finishedWithFailure(message( "Configuration error: @ParameterizedTest consumes 1 parameter but there were 2 arguments provided.%nNote: the provided arguments were [foo, unused1]".formatted())))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @Test @@ -1212,7 +1218,7 @@ void executesWithCsvSourceUnusedArgumentsAndArgumentCountValidationAnnotationAtt "testWithNoneArgumentCountValidation", String.class); results.allEvents().assertThatEvents() // .haveExactly(1, - event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))); } @Test @@ -1220,8 +1226,8 @@ void executesWithMethodSourceProvidingUnusedArguments() { var results = execute(ArgumentCountValidationMode.STRICT, RepeatableSourcesTestCase.class, "testWithRepeatableCsvSource", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) // - .haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b")))); + .haveExactly(1, event(test(), displayName("[1] argument = a"), finishedWithFailure(message("a")))) // + .haveExactly(1, event(test(), displayName("[2] argument = b"), finishedWithFailure(message("b")))); } @Test @@ -1253,9 +1259,9 @@ class RepeatableSourcesIntegrationTests { void executesWithRepeatableCsvFileSource(String methodName) { var results = execute(methodName, String.class, String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, - event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // - .haveExactly(1, event(test(), displayName("[5] column1=FRUIT = apple, column2=RANK = 1"), + .haveExactly(1, event(test(), displayName("[1] column1 = foo, column2 = 1"), + finishedWithFailure(message("foo 1")))) // + .haveExactly(1, event(test(), displayName("[5] column1 = FRUIT = apple, column2 = RANK = 1"), finishedWithFailure(message("apple 1")))); } @@ -1264,8 +1270,8 @@ void executesWithRepeatableCsvFileSource(String methodName) { void executesWithRepeatableCsvSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) // - .haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b")))); + .haveExactly(1, event(test(), displayName("[1] argument = a"), finishedWithFailure(message("a")))) // + .haveExactly(1, event(test(), displayName("[2] argument = b"), finishedWithFailure(message("b")))); } @ParameterizedTest @@ -1274,9 +1280,9 @@ void executesWithRepeatableMethodSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, - event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // + event(test(), displayName("[1] argument = some"), finishedWithFailure(message("some")))) // .haveExactly(1, - event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); + event(test(), displayName("[2] argument = other"), finishedWithFailure(message("other")))); } @ParameterizedTest @@ -1284,9 +1290,10 @@ void executesWithRepeatableMethodSource(String methodName) { void executesWithRepeatableEnumSource(String methodName) { var results = execute(methodName, Action.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=FOO"), finishedWithFailure(message("FOO")))) // .haveExactly(1, - event(test(), displayName("[2] argument=BAR"), finishedWithFailure(message("BAR")))); + event(test(), displayName("[1] argument = FOO"), finishedWithFailure(message("FOO")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = BAR"), finishedWithFailure(message("BAR")))); } @ParameterizedTest @@ -1294,9 +1301,10 @@ void executesWithRepeatableEnumSource(String methodName) { void executesWithRepeatableValueSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))); } @ParameterizedTest @@ -1305,9 +1313,9 @@ void executesWithRepeatableFieldSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, - event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // + event(test(), displayName("[1] argument = some"), finishedWithFailure(message("some")))) // .haveExactly(1, - event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); + event(test(), displayName("[2] argument = other"), finishedWithFailure(message("other")))); } @ParameterizedTest @@ -1316,11 +1324,14 @@ void executesWithRepeatableFieldSource(String methodName) { void executesWithRepeatableArgumentsSource(String methodName) { var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // - .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))) // - .haveExactly(1, event(test(), displayName("[3] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, - event(test(), displayName("[4] argument=bar"), finishedWithFailure(message("bar")))); + event(test(), displayName("[1] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[2] argument = bar"), finishedWithFailure(message("bar")))) // + .haveExactly(1, + event(test(), displayName("[3] argument = foo"), finishedWithFailure(message("foo")))) // + .haveExactly(1, + event(test(), displayName("[4] argument = bar"), finishedWithFailure(message("bar")))); } @@ -1336,10 +1347,10 @@ void executesWithSameRepeatableAnnotationMultipleTimes() { void executesWithDifferentRepeatableAnnotations() { var results = execute("testWithDifferentRepeatableAnnotations", String.class); results.allEvents().assertThatEvents() // - .haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) // - .haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b")))) // - .haveExactly(1, event(test(), displayName("[3] argument=c"), finishedWithFailure(message("c")))) // - .haveExactly(1, event(test(), displayName("[4] argument=d"), finishedWithFailure(message("d")))); + .haveExactly(1, event(test(), displayName("[1] argument = a"), finishedWithFailure(message("a")))) // + .haveExactly(1, event(test(), displayName("[2] argument = b"), finishedWithFailure(message("b")))) // + .haveExactly(1, event(test(), displayName("[3] argument = c"), finishedWithFailure(message("c")))) // + .haveExactly(1, event(test(), displayName("[4] argument = d"), finishedWithFailure(message("d")))); } private EngineExecutionResults execute(String methodName, Class... methodParameterTypes) { @@ -1383,8 +1394,8 @@ void executesTwoIterationsBasedOnIterationAndUniqueIdSelector() { results.allEvents().assertThatEvents() // .haveExactly(2, event(test(), finishedWithFailure())) // - .haveExactly(1, event(test(), displayName("[2] argument=3"), finishedWithFailure())) // - .haveExactly(1, event(test(), displayName("[3] argument=5"), finishedWithFailure())); + .haveExactly(1, event(test(), displayName("[2] argument = 3"), finishedWithFailure())) // + .haveExactly(1, event(test(), displayName("[3] argument = 5"), finishedWithFailure())); } @Nested diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt index 7da01fb28742..87fd06c4b00d 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt @@ -21,9 +21,9 @@ class ParameterizedInvocationNameFormatterIntegrationTests { info: TestInfo ) { if (param.equals("foo")) { - assertEquals("[1] param=foo", info.displayName) + assertEquals("[1] param = foo", info.displayName) } else { - assertEquals("[2] param=bar", info.displayName) + assertEquals("[2] param = bar", info.displayName) } } diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt index c43a26d46a8d..bc5aa646a35f 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt @@ -35,6 +35,6 @@ object DisplayNameTests { number: Int, info: TestInfo ) { - assertEquals("[$number] char=$char, number=$number", info.displayName) + assertEquals("[$number] char = $char, number = $number", info.displayName) } } From 0d0235f7a6fad92e98fb1ecce0a8b1b5a3bd4296 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 21 Jul 2025 20:41:48 +0300 Subject: [PATCH 121/162] Polish release notes for 6.0 M2 --- .../release-notes/release-notes-6.0.0-M2.adoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index acdeab41d797..df6017ccf2dc 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -41,7 +41,7 @@ repository on GitHub. * Introduce new `Launcher.execute({LauncherExecutionRequest})` API with corresponding `LauncherExecutionRequestBuilder` to enable the addition of parameters to test - executions without additional overloads of `execute`. + executions without additional overloads of `execute(...)`. * Introduce `LauncherDiscoveryRequestBuilder.forExecution()` method as a convenience method for constructing a `{LauncherExecutionRequest}` that contains a `{LauncherDiscoveryRequest}`. @@ -54,7 +54,7 @@ repository on GitHub. now causes test execution to be cancelled after the first failed test. * Provide cancellation support for implementations of `{HierarchicalTestEngine}` such as JUnit Jupiter, Spock, and Cucumber. -* Provide cancellation support for the Suite and Vintage test engines +* Provide cancellation support for the `@Suite` test engine. * Introduce `TestTask.getTestDescriptor()` method for use in `HierarchicalTestExecutorService` implementations. * Introduce `computeIfAbsent(...)` methods in `NamespacedHierarchicalStore` to simplify @@ -72,11 +72,11 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes -* Change return type of `provideTestTemplateInvocationContexts(ExtensionContext)` method - of the `TestTemplateInvocationContextProvider` interface from +* Change the return type of the `provideTestTemplateInvocationContexts(ExtensionContext)` + method in the `TestTemplateInvocationContextProvider` interface from `Stream` to `Stream`. -* Remove support for `junit.jupiter.params.arguments.conversion.locale.format` +* Remove support for the `junit.jupiter.params.arguments.conversion.locale.format` configuration parameter. `Locale` conversions are now always performed using the IETF BCP 47 language tag format supported by the `Locale.forLanguageTag(String)` factory method. @@ -100,7 +100,7 @@ repository on GitHub. `fruit = apple` instead of `fruit=apple`. * Reason strings supplied to `ConditionEvaluationResult` APIs are now officially declared as `@Nullable`. -* Introduce `computeIfAbsent(...)` methods in `ExtensionContext.Store` to ease simplify +* Introduce `computeIfAbsent(...)` methods in `ExtensionContext.Store` to simplify working with non-nullable types. @@ -120,4 +120,4 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Provide cancellation support for the Vintage test engine. From 9fecae5a1550d850f8e32a003c42e6b1f113c4b9 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 07:52:22 +0200 Subject: [PATCH 122/162] Adjust integration tests to new display names --- .../support/tests/GradleStarterTests.java | 24 ++-- .../open-test-report.xml.snapshot | 112 +++++++++--------- .../open-test-report.xml.snapshot | 110 ++++++++--------- .../open-test-report.xml.snapshot | 112 +++++++++--------- 4 files changed, 179 insertions(+), 179 deletions(-) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java index 62ad2b94cd6d..0359d40bc8c8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java @@ -54,12 +54,12 @@ void buildJupiterStarterProject(@FilePrefix("gradle") OutputFiles outputFiles, S assertThat(result.stdOut()) // .contains( // - "CalculatorParameterizedClassTests > [1] i=1 > parameterizedTest(int)", // - "CalculatorParameterizedClassTests > [1] i=1 > Inner > [1] 1 > regularTest() PASSED", // - "CalculatorParameterizedClassTests > [1] i=1 > Inner > [2] 2 > regularTest() PASSED", // - "CalculatorParameterizedClassTests > [2] i=2 > parameterizedTest(int)", // - "CalculatorParameterizedClassTests > [2] i=2 > Inner > [1] 1 > regularTest() PASSED", // - "CalculatorParameterizedClassTests > [2] i=2 > Inner > [2] 2 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [1] i = 1 > parameterizedTest(int)", // + "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [1] 1 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [2] 2 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [2] i = 2 > parameterizedTest(int)", // + "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [1] 1 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [2] 2 > regularTest() PASSED", // "Using Java version: 17", // "CalculatorTests > 1 + 1 = 2 PASSED", // "CalculatorTests > add(int, int, int) > 0 + 1 = 1 PASSED", // @@ -79,10 +79,10 @@ void runOnlyOneMethodInClassTemplate(@FilePrefix("gradle") OutputFiles outputFil assertThat(result.stdOut()) // .contains( // - "CalculatorParameterizedClassTests > [1] i=1 > Inner > [1] 1 > regularTest() PASSED", // - "CalculatorParameterizedClassTests > [1] i=1 > Inner > [2] 2 > regularTest() PASSED", // - "CalculatorParameterizedClassTests > [2] i=2 > Inner > [1] 1 > regularTest() PASSED", // - "CalculatorParameterizedClassTests > [2] i=2 > Inner > [2] 2 > regularTest() PASSED" // + "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [1] 1 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [1] i = 1 > Inner > [2] 2 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [1] 1 > regularTest() PASSED", // + "CalculatorParameterizedClassTests > [2] i = 2 > Inner > [2] 2 > regularTest() PASSED" // ) // .doesNotContain("parameterizedTest(int)", "CalculatorTests"); @@ -90,8 +90,8 @@ void runOnlyOneMethodInClassTemplate(@FilePrefix("gradle") OutputFiles outputFil assertThat(result.stdOut()) // .contains( // - "CalculatorParameterizedClassTests > [1] i=1 > parameterizedTest(int)", // - "CalculatorParameterizedClassTests > [2] i=2 > parameterizedTest(int)" // + "CalculatorParameterizedClassTests > [1] i = 1 > parameterizedTest(int)", // + "CalculatorParameterizedClassTests > [2] i = 2 > parameterizedTest(int)" // ) // .doesNotContain("regularTest()", "CalculatorTests"); } diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot index b6193859beec..7b1b7c89b5a0 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/AntStarterTests_snapshots/open-test-report.xml.snapshot @@ -16,12 +16,12 @@ test-method: ant_starter obfuscated Linux 16 - 21.0.6 + 24.0.1 UTF-8 - + - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests] com.example.project.CalculatorParameterizedClassTests @@ -40,7 +40,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests[1] @@ -51,7 +51,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)] parameterizedTest(int) @@ -62,7 +62,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1] @@ -73,11 +73,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2] @@ -88,15 +88,15 @@ test-method: ant_starter - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner @@ -107,7 +107,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1] @@ -118,7 +118,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest() @@ -129,15 +129,15 @@ test-method: ant_starter - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2] @@ -148,7 +148,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest() @@ -159,23 +159,23 @@ test-method: ant_starter - + - + - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests[2] @@ -186,7 +186,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)] parameterizedTest(int) @@ -197,7 +197,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1] @@ -208,11 +208,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2] @@ -223,15 +223,15 @@ test-method: ant_starter - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner @@ -242,7 +242,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1] @@ -253,7 +253,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest() @@ -264,15 +264,15 @@ test-method: ant_starter - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2] @@ -283,7 +283,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest() @@ -294,27 +294,27 @@ test-method: ant_starter - + - + - + - + - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -325,7 +325,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -336,11 +336,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -351,7 +351,7 @@ test-method: ant_starter - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -362,11 +362,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -377,11 +377,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -392,11 +392,11 @@ test-method: ant_starter - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -407,19 +407,19 @@ test-method: ant_starter - + - + - + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot index 09377aa77dd2..fb1aa5f56972 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/GradleStarterTests_snapshots/open-test-report.xml.snapshot @@ -16,12 +16,12 @@ test-method: buildJupiterStarterProject obfuscated Linux 16 - 17.0.14 + 17.0.15 UTF-8 - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests] com.example.project.CalculatorParameterizedClassTests @@ -40,7 +40,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests[1] @@ -51,7 +51,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)] parameterizedTest(int) @@ -62,7 +62,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1] @@ -73,11 +73,11 @@ test-method: buildJupiterStarterProject - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2] @@ -88,15 +88,15 @@ test-method: buildJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner @@ -107,7 +107,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1] @@ -118,7 +118,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest() @@ -129,15 +129,15 @@ test-method: buildJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2] @@ -148,7 +148,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest() @@ -159,23 +159,23 @@ test-method: buildJupiterStarterProject - + - + - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests[2] @@ -186,7 +186,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)] parameterizedTest(int) @@ -197,7 +197,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1] @@ -208,11 +208,11 @@ test-method: buildJupiterStarterProject - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2] @@ -223,15 +223,15 @@ test-method: buildJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner @@ -242,7 +242,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1] @@ -253,7 +253,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest() @@ -264,15 +264,15 @@ test-method: buildJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2] @@ -283,7 +283,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest() @@ -294,27 +294,27 @@ test-method: buildJupiterStarterProject - + - + - + - + - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -325,7 +325,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -336,11 +336,11 @@ test-method: buildJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -351,7 +351,7 @@ test-method: buildJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -362,11 +362,11 @@ test-method: buildJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -377,11 +377,11 @@ test-method: buildJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -392,11 +392,11 @@ test-method: buildJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -407,19 +407,19 @@ test-method: buildJupiterStarterProject - + - + - + - + diff --git a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot index ee2d20a63f1c..0288d1136358 100644 --- a/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot +++ b/platform-tooling-support-tests/src/test/resources/platform/tooling/support/tests/MavenStarterTests_snapshots/open-test-report.xml.snapshot @@ -16,12 +16,12 @@ test-method: verifyJupiterStarterProject obfuscated Linux 16 - 1.8.0_442 + 17.0.15 UTF-8 - + - + [engine:junit-jupiter] JUnit Jupiter @@ -29,7 +29,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests] com.example.project.CalculatorParameterizedClassTests @@ -40,7 +40,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests[1] @@ -51,7 +51,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)] parameterizedTest(int) @@ -62,7 +62,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1] @@ -73,11 +73,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2] @@ -88,15 +88,15 @@ test-method: verifyJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner @@ -107,7 +107,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1] @@ -118,7 +118,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest() @@ -129,15 +129,15 @@ test-method: verifyJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2] @@ -148,7 +148,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#1]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest() @@ -159,23 +159,23 @@ test-method: verifyJupiterStarterProject - + - + - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests[2] @@ -186,7 +186,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)] parameterizedTest(int) @@ -197,7 +197,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#1] parameterizedTest(int)[1] @@ -208,11 +208,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[test-template:parameterizedTest(int)]/[test-template-invocation:#2] parameterizedTest(int)[2] @@ -223,15 +223,15 @@ test-method: verifyJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner] com.example.project.CalculatorParameterizedClassTests$Inner @@ -242,7 +242,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1] com.example.project.CalculatorParameterizedClassTests$Inner[1] @@ -253,7 +253,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#1]/[method:regularTest()] regularTest() @@ -264,15 +264,15 @@ test-method: verifyJupiterStarterProject - + - + - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2] com.example.project.CalculatorParameterizedClassTests$Inner[2] @@ -283,7 +283,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class-template:com.example.project.CalculatorParameterizedClassTests]/[class-template-invocation:#2]/[nested-class-template:Inner]/[class-template-invocation:#2]/[method:regularTest()] regularTest() @@ -294,27 +294,27 @@ test-method: verifyJupiterStarterProject - + - + - + - + - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests] com.example.project.CalculatorTests @@ -325,7 +325,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[method:addsTwoNumbers()] addsTwoNumbers() @@ -336,11 +336,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)] add(int, int, int) @@ -351,7 +351,7 @@ test-method: verifyJupiterStarterProject - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#1] add(int, int, int)[1] @@ -362,11 +362,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#2] add(int, int, int)[2] @@ -377,11 +377,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#3] add(int, int, int)[3] @@ -392,11 +392,11 @@ test-method: verifyJupiterStarterProject - + - + [engine:junit-jupiter]/[class:com.example.project.CalculatorTests]/[test-template:add(int, int, int)]/[test-template-invocation:#4] add(int, int, int)[4] @@ -407,19 +407,19 @@ test-method: verifyJupiterStarterProject - + - + - + - + From 6edf99dda85bdbaf6c166437719b174eb287d56b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 05:54:45 +0000 Subject: [PATCH 123/162] Update dependency com.pinterest.ktlint:ktlint-cli to v1.7.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 377806277be7..ee4dabe0bf46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ jacoco = "0.8.13" jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" -ktlint = "1.7.0" +ktlint = "1.7.1" log4j = "2.25.1" opentest4j = "1.3.0" openTestReporting = "0.2.4" From 31f1a12b2935c06f48f0d7d1ea9a084047e535a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 05:54:49 +0000 Subject: [PATCH 124/162] Update plugin spotless to v7.2.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee4dabe0bf46..f7f53670ce89 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -105,4 +105,4 @@ kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc1" } -spotless = { id = "com.diffplug.spotless", version = "7.2.0" } +spotless = { id = "com.diffplug.spotless", version = "7.2.1" } From 3f85fb4b049fc0c5e7406c17f5b305a8f7cba31c Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 08:19:25 +0200 Subject: [PATCH 125/162] Set locale explicitly to get reliable log output to check against --- .../java/platform/tooling/support/tests/StandaloneTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index 9c63c991b8b7..e2c2bbc1f85b 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -430,6 +430,7 @@ void executeOnJava17(@FilePrefix("console-launcher") OutputFiles outputFiles) th .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // + .addArguments("-Duser.language=en", "-Duser.country=US") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // @@ -463,6 +464,7 @@ void executeOnJava17SelectPackage(@FilePrefix("console-launcher") OutputFiles ou .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // + .addArguments("-Duser.language=en", "-Duser.country=US") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--select-package", Projects.STANDALONE) // From e9467404ee7d8e9ee1f96a6f893705df098c7692 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 08:19:32 +0200 Subject: [PATCH 126/162] Polishing --- .../platform-tooling-support-tests.gradle.kts | 1 - 1 file changed, 1 deletion(-) 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 0663e10ceaeb..2f13dba504a2 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -184,7 +184,6 @@ val test by testing.suites.getting(JvmTestSuite::class) { implementation(testFixtures(projects.junitPlatformReporting)) implementation(libs.snapshotTests.junit5) implementation(libs.snapshotTests.xml) - } targets { From 45465ebd4b4136975910de14c26e76beb68290fc Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 08:31:57 +0200 Subject: [PATCH 127/162] Use Asciidoctor's non-breaking space attribute rather than HTML --- .../src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index df6017ccf2dc..fee0e97d23f7 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -97,7 +97,7 @@ repository on GitHub. * Display names for `@ParameterizedClass` and `@ParameterizedTest` now consistently style name-value pairs for arguments using `name = value` formatting – for example, - `fruit = apple` instead of `fruit=apple`. + `fruit{nbsp}={nbsp}apple` instead of `fruit=apple`. * Reason strings supplied to `ConditionEvaluationResult` APIs are now officially declared as `@Nullable`. * Introduce `computeIfAbsent(...)` methods in `ExtensionContext.Store` to simplify working From 9952f8389de56d27824ba1c67fa6516be5d3541b Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 10:11:45 +0200 Subject: [PATCH 128/162] Finalize 6.0.0-M2 release notes --- .../release-notes/release-notes-6.0.0-M2.adoc | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc index fee0e97d23f7..612f718724a5 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -1,9 +1,15 @@ [[release-notes-6.0.0-M2]] == 6.0.0-M2 -*Date of Release:* ❓ +*Date of Release:* July 22, 2025 -*Scope:* ❓ +*Scope:* + +* New `LauncherExecutionRequest` API +* Support for cancelling test execution via `CancellationToken` +* New `--fail-fast` mode for ConsoleLauncher +* Null-safe `computeIfAbsent` methods for stores +* Strict evaluation of enum-based configuration parameters For a complete list of all _closed_ issues and pull requests for this release, consult the link:{junit-framework-repo}+/milestone/99?closed=1+[6.0.0-M2] milestone page in the JUnit @@ -13,11 +19,6 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-platform]] === JUnit Platform -[[release-notes-6.0.0-M2-junit-platform-bug-fixes]] -==== Bug Fixes - -* ❓ - [[release-notes-6.0.0-M2-junit-platform-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes @@ -64,11 +65,6 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-jupiter]] === JUnit Jupiter -[[release-notes-6.0.0-M2-junit-jupiter-bug-fixes]] -==== Bug Fixes - -* ❓ - [[release-notes-6.0.0-M2-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes @@ -107,16 +103,6 @@ repository on GitHub. [[release-notes-6.0.0-M2-junit-vintage]] === JUnit Vintage -[[release-notes-6.0.0-M2-junit-vintage-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-6.0.0-M2-junit-vintage-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - [[release-notes-6.0.0-M2-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements From ddce2264dad0723ddf897af2d592cc50a38a60c7 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 10:12:24 +0200 Subject: [PATCH 129/162] Release 6.0.0-M2 --- README.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11f5801c2c73..cd487ca936cb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This repository is the home of JUnit Platform, Jupiter, and Vintage. ## Latest Releases - General Availability (GA): [JUnit 5.13.4](https://github.com/junit-team/junit-framework/releases/tag/r5.13.4) (July 21, 2025) -- Preview (Milestone/Release Candidate): [JUnit 6.0.0-M1](https://github.com/junit-team/junit-framework/releases/tag/r6.0.0-M1) (June 27, 2025) +- Preview (Milestone/Release Candidate): [JUnit 6.0.0-M2](https://github.com/junit-team/junit-framework/releases/tag/r6.0.0-M2) (July 22, 2025) ## Documentation diff --git a/gradle.properties b/gradle.properties index 000b32c34896..a0e8d72f8201 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 6.0.0-SNAPSHOT +version = 6.0.0-M2 # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError From 29c08c4a9c921fbcf8ccb6434246a45df5b8acaf Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 10:26:27 +0200 Subject: [PATCH 130/162] Back to snapshots for further development --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a0e8d72f8201..000b32c34896 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 6.0.0-M2 +version = 6.0.0-SNAPSHOT # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError From e1f503f54923b84e30f5cf5d530c7f09e8340032 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 10:35:26 +0200 Subject: [PATCH 131/162] Create initial 6.0.0-RC1 release notes from template --- .../docs/asciidoc/release-notes/index.adoc | 2 + .../release-notes-6.0.0-RC1.adoc | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 1d5ceb6cabee..0423b30a066e 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -17,6 +17,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-6.0.0-RC1.adoc[] + include::{basedir}/release-notes-6.0.0-M2.adoc[] include::{basedir}/release-notes-6.0.0-M1.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc new file mode 100644 index 000000000000..610554d61928 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc @@ -0,0 +1,67 @@ +[[release-notes-6.0.0-RC1]] +== 6.0.0-RC1 + +*Date of Release:* ❓ + +*Scope:* ❓ + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit-framework-repo}+/milestone/102?closed=1+[6.0.0-RC1] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-6.0.0-RC1-junit-platform]] +=== JUnit Platform + +[[release-notes-6.0.0-RC1-junit-platform-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-6.0.0-RC1-junit-platform-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-6.0.0-RC1-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-6.0.0-RC1-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-6.0.0-RC1-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-6.0.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-6.0.0-RC1-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-6.0.0-RC1-junit-vintage]] +=== JUnit Vintage + +[[release-notes-6.0.0-RC1-junit-vintage-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-6.0.0-RC1-junit-vintage-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-6.0.0-RC1-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ From 5778e59ba4c7fb03202af337881c1e4edbc313a1 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Tue, 22 Jul 2025 20:31:28 +0200 Subject: [PATCH 132/162] Improve Javadoc of `ReflectionSupport.tryToGetResources` methods Closes #4791. --- .../commons/support/ReflectionSupport.java | 14 +++++++------ .../commons/util/ReflectionUtils.java | 20 ++----------------- 2 files changed, 10 insertions(+), 24 deletions(-) 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 4e57d4b727d5..245a68619270 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 @@ -104,9 +104,10 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) * * @param classpathResourceName the name of the resource to load; never * {@code null} or blank - * @return a successful {@code Try} containing the loaded resources or a failed - * {@code Try} containing the exception if no such resources could be loaded; - * never {@code null} + * @return a successful {@code Try} containing the set of loaded resources + * (potentially empty) or a failed {@code Try} containing the exception in + * case a failure occurred while trying to list resources; never + * {@code null} * @since 1.12 * @see #tryToGetResources(String, ClassLoader) */ @@ -128,9 +129,10 @@ public static Try> tryToGetResources(String classpathResourceName) * @param classpathResourceName the name of the resource to load; never * {@code null} or blank * @param classLoader the {@code ClassLoader} to use; never {@code null} - * @return a successful {@code Try} containing the loaded resources or a failed - * {@code Try} containing the exception if no such resources could be loaded; - * never {@code null} + * @return a successful {@code Try} containing the set of loaded resources + * (potentially empty) or a failed {@code Try} containing the exception in + * case a failure occurred while trying to list resources; never + * {@code null} * @since 1.12 * @see #tryToGetResources(String) */ 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 858417dd4083..20e51cd9a86d 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 @@ -774,15 +774,7 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } /** - * Try to get {@linkplain Resource resources} by their name, using the - * {@link ClassLoaderUtils#getDefaultClassLoader()}. - * - *

    See {@link org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String)} - * for details. - * - * @param classpathResourceName the name of the resources to load; never {@code null} or blank - * @since 1.12 - * @see org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String, ClassLoader) + * @see org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String) */ @API(status = INTERNAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName) { @@ -790,15 +782,7 @@ public static Try> tryToGetResources(String classpathResourceName) } /** - * Try to get {@linkplain Resource resources} by their name, using the - * supplied {@link ClassLoader}. - * - *

    See {@link org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String, ClassLoader)} - * for details. - * - * @param classpathResourceName the name of the resources to load; never {@code null} or blank - * @param classLoader the {@code ClassLoader} to use; never {@code null} - * @since 1.12 + * @see org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String, ClassLoader) */ @API(status = INTERNAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { From 80bff126ea9df8964e58e53dd3f8457f64bddb4a Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Wed, 23 Jul 2025 12:37:19 +0200 Subject: [PATCH 133/162] Revert "Detect native-image-compatible JDK using Gradle toolchains" This reverts commit dbfc4fdc --- .../platform-tooling-support-tests.gradle.kts | 9 ++------- .../projects/graalvm-starter/gradle.properties | 2 -- .../src/main/java/platform/tooling/support/Helper.java | 5 ----- .../tooling/support/tests/GraalVmStarterTests.java | 9 ++------- 4 files changed, 4 insertions(+), 21 deletions(-) delete mode 100644 platform-tooling-support-tests/projects/graalvm-starter/gradle.properties 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 2f13dba504a2..c819aab758bc 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -236,7 +236,6 @@ val test by testing.suites.getting(JvmTestSuite::class) { val gradleJavaVersion = 21 jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled) - jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled, graalvm = true) systemProperty("gradle.java.version", gradleJavaVersion) } } @@ -263,7 +262,7 @@ class MavenRepo(project: Project, @get:Internal val repoDir: Provider) : C override fun asArguments() = listOf("-Dmaven.repo=${repoDir.get().absolutePath}") } -class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEnabled: Provider, @Input val graalvm: Boolean = false) : CommandLineArgumentProvider { +class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEnabled: Provider) : CommandLineArgumentProvider { @Internal val javaLauncher: Property = project.objects.property() @@ -271,10 +270,6 @@ class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEna try { project.javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(version) - if (graalvm) { - vendor = GRAAL_VM - nativeImageCapable = true - } }.get() } catch (e: Exception) { null @@ -290,7 +285,7 @@ class JavaHomeDir(project: Project, @Input val version: Int, testDistributionEna } val metadata = javaLauncher.map { it.metadata } val javaHome = metadata.map { it.installationPath.asFile.absolutePath }.orNull - return javaHome?.let { listOf("-Djava.home.$version${if (graalvm) ".nativeImage" else ""}=$it") } ?: emptyList() + return javaHome?.let { listOf("-Djava.home.$version=$it") } ?: emptyList() } } diff --git a/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties b/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties deleted file mode 100644 index d33cdba6790d..000000000000 --- a/platform-tooling-support-tests/projects/graalvm-starter/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -org.gradle.java.installations.fromEnv=GRAALVM_HOME -org.gradle.java.installations.auto-download=false diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java index af7379ebeb53..d6fd7aba5778 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java @@ -73,11 +73,6 @@ public static Optional getJavaHome(int version) { return sources.filter(Objects::nonNull).findFirst().map(Path::of); } - public static Optional getJavaHomeWithNativeImageSupport(int version) { - var value = System.getProperty("java.home." + version + ".nativeImage"); - return Optional.ofNullable(value).map(Path::of); - } - private Helper() { } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java index 35cd950de960..562065910b72 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -13,8 +13,6 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static platform.tooling.support.Helper.getJavaHomeWithNativeImageSupport; -import static platform.tooling.support.ProcessStarters.getGradleJavaVersion; import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; @@ -22,10 +20,10 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.tests.process.OutputFiles; -import org.opentest4j.TestAbortedException; import platform.tooling.support.MavenRepo; import platform.tooling.support.ProcessStarters; @@ -35,18 +33,15 @@ */ @Order(Integer.MIN_VALUE) @DisabledOnOpenJ9 +@EnabledIfEnvironmentVariable(named = "GRAALVM_HOME", matches = ".+") class GraalVmStarterTests { @Test @Timeout(value = 10, unit = MINUTES) void runsTestsInNativeImage(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { - - var graalVmHome = getJavaHomeWithNativeImageSupport(getGradleJavaVersion()); - var result = ProcessStarters.gradlew() // .workingDir(copyToWorkspace(Projects.GRAALVM_STARTER, workspace)) // - .putEnvironment("GRAALVM_HOME", graalVmHome.orElseThrow(TestAbortedException::new).toString()) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail", "--refresh-dependencies") // From ea537a0665b5befea7a3a58ef921529aff5adf7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:43:10 +0000 Subject: [PATCH 134/162] Update graalvm/setup-graalvm action to v1.3.5 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 097c882b871b..cd2aeff27388 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,7 +25,7 @@ jobs: with: fetch-depth: 1 - name: Install GraalVM - uses: graalvm/setup-graalvm@e1df20a713a4cc6ab5b0eb03f0e0dcdc0199b805 # v1.3.4 + uses: graalvm/setup-graalvm@7f488cf82a3629ee755e4e97342c01d6bed318fa # v1.3.5 with: distribution: graalvm-community version: 'latest' From 03a75026546833ef267b4f7ef05fa3940a05695d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:50:58 +0000 Subject: [PATCH 135/162] Update plugin shadow to v9.0.0-rc2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f7f53670ce89..5c6dd110dee5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -104,5 +104,5 @@ jreleaser = { id = "org.jreleaser", version = "1.19.0" } kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } -shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc1" } +shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc2" } spotless = { id = "com.diffplug.spotless", version = "7.2.1" } From 365db09d60c63c5dc403b45d16adda14578ef252 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:03:27 +0000 Subject: [PATCH 136/162] Update dependency org.apache.groovy:groovy to v4.0.28 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c6dd110dee5..562dba8db4cd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.181" } commons-io = { module = "commons-io:commons-io", version = "2.20.0" } errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.40.0" } fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" } -groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.27" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.28" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } From d6e0a0b156986019a089ef4223243588b40bbe9b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 16:31:04 +0000 Subject: [PATCH 137/162] Update github/codeql-action action to v3.29.4 --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d3fa15ef67de..ba464b02f0a9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -53,6 +53,6 @@ jobs: -Dscan.tag.CodeQL \ classes - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c60b0ba136b5..48d98f5cfd73 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: sarif_file: results.sarif From ebb561acdc79db2c26210c3bd059f349a59d1750 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Thu, 24 Jul 2025 08:15:32 +0200 Subject: [PATCH 138/162] Instantiate at most one ServiceLoader-provided ClasspathScanner --- .../platform/commons/util/ClasspathScannerLoader.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java index e11d06e885da..be4232249b4d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java @@ -10,10 +10,9 @@ package org.junit.platform.commons.util; -import static java.util.stream.StreamSupport.stream; - import java.util.List; import java.util.ServiceLoader; +import java.util.ServiceLoader.Provider; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.scanning.ClasspathScanner; @@ -28,16 +27,17 @@ static ClasspathScanner getInstance() { ServiceLoader serviceLoader = ServiceLoader.load(ClasspathScanner.class, ClassLoaderUtils.getDefaultClassLoader()); - List classpathScanners = stream(serviceLoader.spliterator(), false).toList(); + List> classpathScanners = serviceLoader.stream().toList(); if (classpathScanners.size() == 1) { - return classpathScanners.get(0); + return classpathScanners.get(0).get(); } if (classpathScanners.size() > 1) { throw new JUnitException( "There should not be more than one ClasspathScanner implementation present on the classpath but there were %d: %s".formatted( - classpathScanners.size(), classpathScanners)); + classpathScanners.size(), + classpathScanners.stream().map(Provider::type).map(Class::getName).toList())); } return new DefaultClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); From 8bd68885a8e71ebe6af0cf34904c50e90ea81599 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 31 Jul 2025 11:40:46 +0300 Subject: [PATCH 139/162] Fix formatting in example --- .../src/test/java/example/ParameterizedTestDemo.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index dc41e179994d..e8a0e98c9f6e 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -314,10 +314,10 @@ void testWithMultiArgFieldSource(String str, int num, List list) { // tag::CsvSource_example[] @ParameterizedTest @CsvSource({ - "apple, 1", - "banana, 2", + "apple, 1", + "banana, 2", "'lemon, lime', 0xF1", - "strawberry, 700_000" + "strawberry, 700_000" }) void testWithCsvSource(String fruit, int rank) { assertNotNull(fruit); From 50a82401499533d9906a7d6cb7b264eff49a657c Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:19:56 +0300 Subject: [PATCH 140/162] Quote text-based arguments in display names for parameterized tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces quoteTextArguments attributes in @⁠ParameterizedClass and @⁠ParameterizedTest which default to true. This new feature automatically encloses any CharSequence (such as a String) in double quotes (") and any Character in single quotes ('). Special characters will be escaped within quoted text. For example, '\n', '\r', will be escaped as "\\r" and "\\n", respectively. In addition, any ISO control character will be represented as a question mark (?) in the quoted text. For example, given a String argument "line 1\nline 2", the representation in the display name would be "\"line 1\\nline 2\"" (visually "line 1\nline 2") with the newline character escaped as "\\n". Similarly, given a String argument "\t", the representation in the display name would be "\"\\t\"" (visually "\t") instead of a blank string or invisible tab character. The same applies for a character argument '\t', whose representation in the display name would be "'\\t'" (visually '\t'). Closes #4716 --- .../release-notes-6.0.0-RC1.adoc | 5 +- .../asciidoc/user-guide/writing-tests.adoc | 148 +++++++++++++----- .../java/example/ParameterizedTestDemo.java | 2 +- ...izedInvocationNameFormatterBenchmarks.java | 2 +- .../jupiter/params/EvaluatedArgumentSet.java | 4 +- .../jupiter/params/ParameterizedClass.java | 41 +++++ .../params/ParameterizedClassContext.java | 5 + .../ParameterizedDeclarationContext.java | 2 + .../ParameterizedInvocationContext.java | 2 +- .../ParameterizedInvocationNameFormatter.java | 41 +++-- .../jupiter/params/ParameterizedTest.java | 42 +++++ .../params/ParameterizedTestContext.java | 5 + .../org/junit/jupiter/params/QuoteUtils.java | 55 +++++++ .../params/provider/CsvArgumentsProvider.java | 7 +- .../support/ParameterNameAndArgument.java | 61 ++++++++ .../ConditionEvaluationResultTests.java | 4 +- .../ParameterizedClassIntegrationTests.java | 23 +-- ...meterizedInvocationNameFormatterTests.java | 133 +++++++++++++--- .../ParameterizedTestIntegrationTests.java | 121 +++++++------- .../provider/CsvArgumentsProviderTests.java | 8 +- .../CsvFileArgumentsProviderTests.java | 15 +- ...InvocationNameFormatterIntegrationTests.kt | 14 +- .../params/aggregator/DisplayNameTests.kt | 8 +- .../com/example/project/CalculatorTests.java | 2 +- .../com/example/project/CalculatorTests.java | 2 +- .../standalone/JupiterParamsIntegration.java | 2 +- .../tests/JarContainsManifestFirstTests.java | 2 +- .../support/tests/JarDescribeModuleTests.java | 4 +- .../tooling/support/tests/ManifestTests.java | 2 +- .../tests/UnalignedClasspathTests.java | 2 +- .../tests/VintageGradleIntegrationTests.java | 2 +- .../tests/VintageMavenIntegrationTests.java | 2 +- 32 files changed, 597 insertions(+), 171 deletions(-) create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/QuoteUtils.java create mode 100644 junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterNameAndArgument.java diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc index 610554d61928..c39c0e9621b5 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc @@ -45,7 +45,10 @@ repository on GitHub. [[release-notes-6.0.0-RC1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements -* ❓ +* Text-based arguments in display names for parameterized tests are now quoted by default. + In addition, special characters are escaped within quoted text. Please refer to the + <<../user-guide/index.adoc#writing-tests-parameterized-tests-display-names-quoted-text, + User Guide>> for details. [[release-notes-6.0.0-RC1-junit-vintage]] diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 68aaf52731b6..c699d2a393eb 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1242,26 +1242,26 @@ Executing the above test class yields the following output: .... FruitTests ✔ -├─ [1] fruit = apple ✔ +├─ [1] fruit = "apple" ✔ │ └─ QuantityTests ✔ │ ├─ [1] quantity = 23 ✔ │ │ └─ test(Duration) ✔ -│ │ ├─ [1] duration = PT1H ✔ -│ │ └─ [2] duration = PT2H ✔ +│ │ ├─ [1] duration = "PT1H" ✔ +│ │ └─ [2] duration = "PT2H" ✔ │ └─ [2] quantity = 42 ✔ │ └─ test(Duration) ✔ -│ ├─ [1] duration = PT1H ✔ -│ └─ [2] duration = PT2H ✔ -└─ [2] fruit = banana ✔ +│ ├─ [1] duration = "PT1H" ✔ +│ └─ [2] duration = "PT2H" ✔ +└─ [2] fruit = "banana" ✔ └─ QuantityTests ✔ ├─ [1] quantity = 23 ✔ │ └─ test(Duration) ✔ - │ ├─ [1] duration = PT1H ✔ - │ └─ [2] duration = PT2H ✔ + │ ├─ [1] duration = "PT1H" ✔ + │ └─ [2] duration = "PT2H" ✔ └─ [2] quantity = 42 ✔ └─ test(Duration) ✔ - ├─ [1] duration = PT1H ✔ - └─ [2] duration = PT2H ✔ + ├─ [1] duration = "PT1H" ✔ + └─ [2] duration = "PT2H" ✔ .... [[writing-tests-dependency-injection]] @@ -1649,9 +1649,9 @@ following. .... palindromes(String) ✔ -├─ [1] candidate = racecar ✔ -├─ [2] candidate = radar ✔ -└─ [3] candidate = able was I ere I saw elba ✔ +├─ [1] candidate = "racecar" ✔ +├─ [2] candidate = "radar" ✔ +└─ [3] candidate = "able was I ere I saw elba" ✔ .... The same `@ValueSource` annotation can be used to specify the source of arguments for a @@ -1668,13 +1668,13 @@ following. .... PalindromeTests ✔ -├─ [1] candidate = racecar ✔ +├─ [1] candidate = "racecar" ✔ │ ├─ palindrome() ✔ │ └─ reversePalindrome() ✔ -├─ [2] candidate = radar ✔ +├─ [2] candidate = "radar" ✔ │ ├─ palindrome() ✔ │ └─ reversePalindrome() ✔ -└─ [3] candidate = able was I ere I saw elba ✔ +└─ [3] candidate = "able was I ere I saw elba" ✔ ├─ palindrome() ✔ └─ reversePalindrome() ✔ .... @@ -2139,7 +2139,7 @@ It is also possible to provide a `Stream`, `DoubleStream`, `IntStream`, `LongStr iterator is wrapped in a `java.util.function.Supplier`. The following example demonstrates how to provide a `Supplier` of a `Stream` of named arguments. This parameterized test method will be invoked twice: with the values `"apple"` and `"banana"` and with display -names `Apple` and `Banana`, respectively. +names `"Apple"` and `"Banana"`, respectively. [source,java,indent=0] ---- @@ -2252,10 +2252,10 @@ void testWithCsvSource(String fruit, int rank) { The generated display names for the previous example include the CSV header names. ---- -[1] FRUIT = apple, RANK = 1 -[2] FRUIT = banana, RANK = 2 -[3] FRUIT = lemon, lime, RANK = 0xF1 -[4] FRUIT = strawberry, RANK = 700_000 +[1] FRUIT = "apple", RANK = "1" +[2] FRUIT = "banana", RANK = "2" +[3] FRUIT = "lemon, lime", RANK = "0xF1" +[4] FRUIT = "strawberry", RANK = "700_000" ---- In contrast to CSV records supplied via the `value` attribute, a text block can contain @@ -2332,20 +2332,20 @@ The following listing shows the generated display names for the first two parame test methods above. ---- -[1] country = Sweden, reference = 1 -[2] country = Poland, reference = 2 -[3] country = United States of America, reference = 3 -[4] country = France, reference = 700_000 +[1] country = "Sweden", reference = "1" +[2] country = "Poland", reference = "2" +[3] country = "United States of America", reference = "3" +[4] country = "France", reference = "700_000" ---- The following listing shows the generated display names for the last parameterized test method above that uses CSV header names. ---- -[1] COUNTRY = Sweden, REFERENCE = 1 -[2] COUNTRY = Poland, REFERENCE = 2 -[3] COUNTRY = United States of America, REFERENCE = 3 -[4] COUNTRY = France, REFERENCE = 700_000 +[1] COUNTRY = "Sweden", REFERENCE = "1" +[2] COUNTRY = "Poland", REFERENCE = "2" +[3] COUNTRY = "United States of America", REFERENCE = "3" +[4] COUNTRY = "France", REFERENCE = "700_000" ---- In contrast to the default syntax used in `@CsvSource`, `@CsvFileSource` uses a double @@ -2668,11 +2668,18 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsAggregator_w ==== Customizing Display Names By default, the display name of a parameterized class or test invocation contains the -invocation index and the `String` representation of all arguments for that specific -invocation. Each argument is preceded by its parameter name (unless the argument is only -available via an `ArgumentsAccessor` or `ArgumentAggregator`), if the parameter name is -present in the bytecode (for Java, test code must be compiled with the `-parameters` -compiler flag; for Kotlin, with `-java-parameters`). +invocation index and a comma-separated list of the `String` representations of all +arguments for that specific invocation. If parameter names are present in the bytecode, +each argument will be preceded by its parameter name and an equals sign (unless the +argument is only available via an `ArgumentsAccessor` or `ArgumentAggregator`) – for +example, `firstName = "Jane"`. + +[TIP] +==== +To ensure that parameter names are present in the bytecode, test code must be compiled +with the `-parameters` compiler flag for Java or with the `-java-parameters` compiler flag +for Kotlin. +==== However, you can customize invocation display names via the `name` attribute of the `@ParameterizedClass` or `@ParameterizedTest` annotation as in the following example. @@ -2688,9 +2695,9 @@ the following. .... Display name of container ✔ -├─ 1 ==> the rank of 'apple' is 1 ✔ -├─ 2 ==> the rank of 'banana' is 2 ✔ -└─ 3 ==> the rank of 'lemon, lime' is 3 ✔ +├─ 1 ==> the rank of "apple" is "1" ✔ +├─ 2 ==> the rank of "banana" is "2" ✔ +└─ 3 ==> the rank of "lemon, lime" is "3" ✔ .... ====== @@ -2777,6 +2784,70 @@ Note that `argumentSet(String, Object...)` is a static factory method defined in `org.junit.jupiter.params.provider.Arguments` interface. ==== +[[writing-tests-parameterized-tests-display-names-quoted-text]] +===== Quoted Text-based Arguments + +As of JUnit Jupiter 6.0, text-based arguments in display names for parameterized tests are +quoted by default. In this context, any `CharSequence` (such as a `String`) or `Character` +is considered text. A `CharSequence` is wrapped in double quotes (`"`), and a `Character` +is wrapped in single quotes (`'`). + +Special characters will be escaped in the quoted text. For example, carriage returns and +line feeds will be escaped as `\\r` and `\\n`, respectively. In addition, any ISO control +character will be represented as a question mark (`?`) in the quoted text. + +[TIP] +==== +This feature can be disabled by setting the `quoteTextArguments` attributes in +`@ParameterizedClass` and `@ParameterizedTest` to `false`. +==== + +For example, given a string argument `"line 1\nline 2"`, the physical representation in +the display name will be `"\"line 1\\nline 2\""` which is printed as `"line 1\nline 2"`. +Similarly, given a string argument `"\t"`, the physical representation in the display name +will be `"\"\\t\""` which is printed as `"\t"` instead of a blank string or invisible tab +character. The same applies for a character argument `'\t'`, whose physical representation +in the display name would be `"'\\t'"` which is printed as `'\t'`. + +For a concrete example, if you run the first `nullEmptyAndBlankStrings(String text)` +parameterized test method from the +<> section above, the following +display names are generated. + +---- +[1] text = null +[2] text = "" +[3] text = " " +[4] text = " " +[5] text = "\t" +[6] text = "\n" +---- + +If you run the first `testWithCsvSource(String fruit, int rank)` parameterized test method +from the <> section above, the +following display names are generated. + +---- +[1] fruit = "apple", rank = "1" +[2] fruit = "banana", rank = "2" +[3] fruit = "lemon, lime", rank = "0xF1" +[4] fruit = "strawberry", rank = "700_000" +---- + +[NOTE] +==== +The original source arguments are quoted when generating a display name, and this occurs +before any implicit or explicit argument conversion is performed. + +For example, if a parameterized test accepts `3.14` as a `float` argument that was +converted from `"3.14"` as an input string, `"3.14"` will be present in the display name +instead of `3.14`. You can see the effect of this with the `rank` values in the above +example. +==== + +[[writing-tests-parameterized-tests-display-names-default-pattern]] +===== Default Display Name Pattern + If you'd like to set a default name pattern for all parameterized classes and tests in your project, you can declare the `junit.jupiter.params.displayname.default` configuration parameter in the `junit-platform.properties` file as demonstrated in the following example (see @@ -2787,6 +2858,9 @@ parameter in the `junit-platform.properties` file as demonstrated in the followi junit.jupiter.params.displayname.default = {index} ---- +[[writing-tests-parameterized-tests-display-names-precedence-rules]] +===== Precedence Rules + The display name for a parameterized class or test is determined according to the following precedence rules: diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index e8a0e98c9f6e..f85b9984ce10 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -582,7 +582,7 @@ void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { // tag::custom_display_names[] @DisplayName("Display name of container") - @ParameterizedTest(name = "{index} ==> the rank of ''{0}'' is {1}") + @ParameterizedTest(name = "{index} ==> the rank of {0} is {1}") @CsvSource({ "apple, 1", "banana, 2", "'lemon, lime', 3" }) void testWithCustomDisplayNames(String fruit, int rank) { } 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 0d57127007e1..069caa616320 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 @@ -56,7 +56,7 @@ public void formatTestNames(Blackhole blackhole) throws Exception { 512); for (int i = 0; i < argumentsList.size(); i++) { Arguments arguments = argumentsList.get(i); - blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments))); + blackhole.consume(formatter.format(i, EvaluatedArgumentSet.allOf(arguments), false)); } } 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 de8573073b55..aff3e054f27d 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 @@ -75,8 +75,8 @@ int getConsumedLength() { } @Nullable - Object[] getConsumedNames() { - return extractFromNamed(this.consumed, Named::getName); + Object[] getConsumedArguments() { + return this.consumed; } @Nullable diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClass.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClass.java index b01a72c12259..4a64a181c713 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClass.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClass.java @@ -198,9 +198,50 @@ * a flag rather than a placeholder. * * @see java.text.MessageFormat + * @see #quoteTextArguments() */ String name() default ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME; + /** + * Configure whether to enclose text-based argument values in quotes within + * display names. + * + *

    Defaults to {@code true}. + * + *

    In this context, any {@link CharSequence} (such as a {@link String}) + * or {@link Character} is considered text. A {@code CharSequence} is wrapped + * in double quotes ("), and a {@code Character} is wrapped in single quotes + * ('). + * + *

    Special characters in Java strings and characters will be escaped in the + * quoted text — for example, carriage returns and line feeds will be + * escaped as {@code \\r} and {@code \\n}, respectively. In addition, any + * {@linkplain Character#isISOControl(char) ISO control character} will be + * represented as a question mark (?) in the quoted text. + * + *

    For example, given a string argument {@code "line 1\nline 2"}, the + * representation in the display name would be {@code "\"line 1\\nline 2\""} + * (printed as {@code "line 1\nline 2"}) with the newline character escaped as + * {@code "\\n"}. Similarly, given a string argument {@code "\t"}, the + * representation in the display name would be {@code "\"\\t\""} (printed as + * {@code "\t"}) instead of a blank string or invisible tab + * character. The same applies for a character argument {@code '\t'}, whose + * representation in the display name would be {@code "'\\t'"} (printed as + * {@code '\t'}). + * + *

    Please note that original source arguments are quoted when generating + * a display name, before any implicit or explicit argument conversion is + * performed. For example, if a parameterized class accepts {@code 3.14} as a + * {@code float} argument that was converted from {@code "3.14"} as an input + * string, {@code "3.14"} will be present in the display name instead of + * {@code 3.14}. + * + * @since 6.0 + * @see #name() + */ + @API(status = EXPERIMENTAL, since = "6.0") + boolean quoteTextArguments() default true; + /** * Configure whether all arguments of the parameterized class that implement * {@link AutoCloseable} will be closed after their corresponding diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java index 760291dd8c59..f0402064a5a8 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java @@ -102,6 +102,11 @@ public String getDisplayNamePattern() { return this.annotation.name(); } + @Override + public boolean quoteTextArguments() { + return this.annotation.quoteTextArguments(); + } + @Override public boolean isAutoClosingArguments() { return this.annotation.autoCloseArguments(); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedDeclarationContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedDeclarationContext.java index 11dad62adc72..3a8884c5bb31 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedDeclarationContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedDeclarationContext.java @@ -28,6 +28,8 @@ interface ParameterizedDeclarationContext { String getDisplayNamePattern(); + boolean quoteTextArguments(); + boolean isAutoClosingArguments(); boolean isAllowingZeroInvocations(); 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 ce9e38931655..1b8e0ae36882 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 @@ -43,7 +43,7 @@ class ParameterizedInvocationContext argumentSetName) { + Optional argumentSetName, boolean quoteTextArguments) { } @FunctionalInterface @@ -246,24 +247,42 @@ private static class MessageFormatPartialFormatter implements PartialFormatter { // synchronized because MessageFormat is not thread-safe @Override public synchronized void append(ArgumentsContext context, StringBuffer result) { - this.messageFormat.format(makeReadable(context.consumedArguments), result, new FieldPosition(0)); + this.messageFormat.format(makeReadable(context.consumedArguments, context.quoteTextArguments), result, + new FieldPosition(0)); } - private @Nullable Object[] makeReadable(@Nullable Object[] arguments) { + private @Nullable Object[] makeReadable(@Nullable Object[] arguments, boolean quoteTextArguments) { @Nullable 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) { - result[i] = truncateIfExceedsMaxLength(StringUtils.nullSafeToString(arguments[i])); + Object argument = arguments[i]; + String prefix = ""; + + if (argument instanceof ParameterNameAndArgument parameterNameAndArgument) { + prefix = parameterNameAndArgument.getName() + " = "; + argument = parameterNameAndArgument.getPayload(); + } + + if (argument instanceof Character ch) { + result[i] = prefix + (quoteTextArguments ? QuoteUtils.quote(ch) : ch); + } + else { + String argumentText = (argument == null ? "null" + : truncateIfExceedsMaxLength(StringUtils.nullSafeToString(argument))); + result[i] = prefix + (quoteTextArguments && argument instanceof CharSequence// + ? QuoteUtils.quote(argumentText) + : argumentText); + } } } return result; } - private @Nullable String truncateIfExceedsMaxLength(@Nullable String argument) { - if (argument != null && argument.length() > this.argumentMaxLength) { + private String truncateIfExceedsMaxLength(String argument) { + if (argument.length() > this.argumentMaxLength) { return argument.substring(0, this.argumentMaxLength - 1) + ELLIPSIS; } return argument; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java index 9707229f6674..68b8f3d41918 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java @@ -11,6 +11,7 @@ package org.junit.jupiter.params; import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -281,9 +282,50 @@ * a flag rather than a placeholder. * * @see java.text.MessageFormat + * @see #quoteTextArguments() */ String name() default ParameterizedInvocationNameFormatter.DEFAULT_DISPLAY_NAME; + /** + * Configure whether to enclose text-based argument values in quotes within + * display names. + * + *

    Defaults to {@code true}. + * + *

    In this context, any {@link CharSequence} (such as a {@link String}) + * or {@link Character} is considered text. A {@code CharSequence} is wrapped + * in double quotes ("), and a {@code Character} is wrapped in single quotes + * ('). + * + *

    Special characters in Java strings and characters will be escaped in the + * quoted text — for example, carriage returns and line feeds will be + * escaped as {@code \\r} and {@code \\n}, respectively. In addition, any + * {@linkplain Character#isISOControl(char) ISO control character} will be + * represented as a question mark (?) in the quoted text. + * + *

    For example, given a string argument {@code "line 1\nline 2"}, the + * representation in the display name would be {@code "\"line 1\\nline 2\""} + * (printed as {@code "line 1\nline 2"}) with the newline character escaped as + * {@code "\\n"}. Similarly, given a string argument {@code "\t"}, the + * representation in the display name would be {@code "\"\\t\""} (printed as + * {@code "\t"}) instead of a blank string or invisible tab + * character. The same applies for a character argument {@code '\t'}, whose + * representation in the display name would be {@code "'\\t'"} (printed as + * {@code '\t'}). + * + *

    Please note that original source arguments are quoted when generating + * a display name, before any implicit or explicit argument conversion is + * performed. For example, if a parameterized test accepts {@code 3.14} as a + * {@code float} argument that was converted from {@code "3.14"} as an input + * string, {@code "3.14"} will be present in the display name instead of + * {@code 3.14}. + * + * @since 6.0 + * @see #name() + */ + @API(status = EXPERIMENTAL, since = "6.0") + boolean quoteTextArguments() default true; + /** * Configure whether all arguments of the parameterized test that implement * {@link AutoCloseable} will be closed after their corresponding diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java index aecbe3c57de6..ab4213c57651 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestContext.java @@ -55,6 +55,11 @@ public String getDisplayNamePattern() { return this.annotation.name(); } + @Override + public boolean quoteTextArguments() { + return this.annotation.quoteTextArguments(); + } + @Override public boolean isAutoClosingArguments() { return this.annotation.autoCloseArguments(); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/QuoteUtils.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/QuoteUtils.java new file mode 100644 index 000000000000..a280daa9712e --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/QuoteUtils.java @@ -0,0 +1,55 @@ +/* + * 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.jupiter.params; + +/** + * Collection of utilities for quoting text. + * + * @since 6.0 + */ +final class QuoteUtils { + + private QuoteUtils() { + /* no-op */ + } + + public static String quote(CharSequence text) { + if (text.isEmpty()) { + return "\"\""; + } + StringBuilder builder = new StringBuilder(); + builder.append('"'); + for (int i = 0; i < text.length(); i++) { + builder.append(escape(text.charAt(i), true)); + } + builder.append('"'); + return builder.toString(); + } + + public static String quote(char ch) { + return '\'' + escape(ch, false) + '\''; + } + + private static String escape(char ch, boolean withinString) { + return switch (ch) { + case '"' -> withinString ? "\\\"" : "\""; + case '\'' -> withinString ? "'" : "\\'"; + case '\\' -> "\\\\"; + case '\b' -> "\\b"; + case '\f' -> "\\f"; + case '\t' -> "\\t"; + case '\r' -> "\\r"; + case '\n' -> "\\n"; + default -> Character.isISOControl(ch) ? "?" : String.valueOf(ch); + }; + } + +} 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 0b591f49ef08..49f330538c46 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 @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Named; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.support.ParameterDeclarations; +import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.UnrecoverableExceptions; @@ -90,7 +91,7 @@ static Arguments processCsvRecord(CsvRecord record, boolean useHeadersInDisplayN Object argument = resolveNullMarker(fields.get(i)); if (useHeadersInDisplayName) { String header = resolveNullMarker(headers.get(i)); - argument = asNamed(header + " = " + argument, argument); + argument = new ParameterNameAndArgument(String.valueOf(header), argument); } arguments[i] = argument; } @@ -107,10 +108,6 @@ private static List getHeaders(CsvRecord record) { return record == CsvReaderFactory.DefaultFieldModifier.NULL_MARKER ? null : record; } - private static Named<@Nullable Object> asNamed(String name, @Nullable Object column) { - return Named.<@Nullable Object> 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/support/ParameterNameAndArgument.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterNameAndArgument.java new file mode 100644 index 000000000000..850b5b6f192a --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/ParameterNameAndArgument.java @@ -0,0 +1,61 @@ +/* + * 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.jupiter.params.support; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import org.apiguardian.api.API; +import org.jspecify.annotations.Nullable; +import org.junit.jupiter.api.Named; + +/** + * Customized parameter name and its associated argument value. + * + *

    Although this class implements {@link Named} for technical reasons, it + * serves a different purpose than {@link Named#of(String, Object)} and is only + * used for internal display name processing. + * + * @since 6.0 + */ +@API(status = INTERNAL, since = "6.0") +public class ParameterNameAndArgument implements Named<@Nullable Object> { + + private final String name; + + private final @Nullable Object argument; + + public ParameterNameAndArgument(String name, @Nullable Object argument) { + this.name = name; + this.argument = argument; + } + + /** + * Get the customized name of the parameter. + */ + @Override + public String getName() { + return this.name; + } + + /** + * Get the argument for the parameter. + */ + @Override + public @Nullable Object getPayload() { + return this.argument; + } + + @Override + public String toString() { + return "ParameterNameAndArgument[name = %s, argument = %s]".formatted(this.name, this.argument); + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java index e82e5fa6df53..0db022de717b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -119,9 +119,9 @@ private static void disabledWithDefaultReasonAndCustomReason(String defaultReaso } @Retention(RetentionPolicy.RUNTIME) - @ParameterizedTest(name = "[{index}] reason=\"{0}\"") + @ParameterizedTest @NullSource - @ValueSource(strings = { "", " ", " ", "\t", "\n" }) + @ValueSource(strings = { "", " ", " ", "\t", "\f", "\r", "\n", "\r\n" }) @interface BlankReasonsTest { } 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 8a8c76b8f7b4..fea99691a7a9 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 @@ -176,7 +176,7 @@ void supportsNullAndEmptySource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value = null", "[2] value = "); + .containsExactly("[1] value = null", "[2] value = \"\""); } @ParameterizedTest @@ -188,8 +188,8 @@ void supportsCsvFileSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(10).succeeded(10)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] name = foo, value = 1", "[2] name = bar, value = 2", - "[3] name = baz, value = 3", "[4] name = qux, value = 4"); + .containsExactly("[1] name = \"foo\", value = \"1\"", "[2] name = \"bar\", value = \"2\"", + "[3] name = \"baz\", value = \"3\"", "[4] name = \"qux\", value = \"4\""); } @ParameterizedTest @@ -225,7 +225,7 @@ void supportsMethodSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value = foo", "[2] value = bar"); + .containsExactly("[1] value = \"foo\"", "[2] value = \"bar\""); } @Test @@ -247,7 +247,7 @@ void supportsFieldSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value = foo", "[2] value = bar"); + .containsExactly("[1] value = \"foo\"", "[2] value = \"bar\""); } @Test @@ -269,7 +269,7 @@ void supportsArgumentsSource(Class classTemplateClass) { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("[1] value = foo", "[2] value = bar"); + .containsExactly("[1] value = \"foo\"", "[2] value = \"bar\""); } @Test @@ -305,7 +305,8 @@ void supportsCustomNamePatterns() { results.allEvents().assertStatistics(stats -> stats.started(6).succeeded(6)); assertThat(invocationDisplayNames(results)) // - .containsExactly("1 | TesT | 1, foo | set", "2 | TesT | 2, bar | number = 2, name = bar"); + .containsExactly("1 | TesT | 1, \"foo\" | set", + "2 | TesT | 2, \"bar\" | number = 2, name = \"bar\""); } @Test @@ -398,8 +399,8 @@ void supportsNestedParameterizedClass(Class classTemplateClass) { results.testEvents().assertStatistics(stats -> stats.started(8).succeeded(8)); assertThat(invocationDisplayNames(results)) // .containsExactly( // - "[1] number = 1", "[1] text = foo", "[2] text = bar", // - "[2] number = 2", "[1] text = foo", "[2] text = bar" // + "[1] number = 1", "[1] text = \"foo\"", "[2] text = \"bar\"", // + "[2] number = 2", "[1] text = \"foo\"", "[2] text = \"bar\"" // ); assertThat(allReportEntries(results)).map(it -> it.get("value")).containsExactly( // @formatter:off @@ -903,7 +904,7 @@ void test2() { } @SuppressWarnings("JUnitMalformedDeclaration") - @ParameterizedClass + @ParameterizedClass(quoteTextArguments = false) @CsvSource({ "-1", "1" }) record RecordWithBuiltInConverterTestCase(int value) { @@ -1047,7 +1048,7 @@ void test2() { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) - @ParameterizedClass + @ParameterizedClass(quoteTextArguments = false) @ValueSource(ints = { -1, 1 }) @interface ParameterizedClassWithNegativeAndPositiveValue { } 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 13491d975741..e80fea4a1d2d 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 @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Named.named; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENTS_WITH_NAMES_PLACEHOLDER; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; @@ -49,10 +50,13 @@ import org.junit.jupiter.params.aggregator.SimpleArgumentsAggregator; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.support.ReflectionSupport; /** + * Tests for {@link ParameterizedInvocationNameFormatter}. + * * @since 5.0 */ @SuppressWarnings("ALL") @@ -105,14 +109,14 @@ void defaultDisplayName() { var formattedName = format(formatter, 1, arguments("apple", "banana")); - assertThat(formattedName).isEqualTo("[1] apple, banana"); + assertThat(formattedName).isEqualTo("[1] \"apple\", \"banana\""); } @Test void formatsIndividualArguments() { var formatter = formatter("{0} -> {1}", "enigma"); - assertEquals("foo -> 42", format(formatter, 1, arguments("foo", 42))); + assertEquals("\"foo\" -> 42", format(formatter, 1, arguments("foo", 42))); } @Test @@ -122,7 +126,7 @@ void formatsCompleteArgumentsList() { // @formatter:off Arguments args = arguments( 42, - 99, + '$', "enigma", null, new int[] { 1, 2, 3 }, @@ -131,7 +135,7 @@ void formatsCompleteArgumentsList() { ); // @formatter:on - assertEquals("42, 99, enigma, null, [1, 2, 3], [foo, bar], [[2, 4], [3, 9]]", format(formatter, 1, args)); + assertEquals("42, '$', \"enigma\", null, [1, 2, 3], [foo, bar], [[2, 4], [3, 9]]", format(formatter, 1, args)); } @Test @@ -140,7 +144,7 @@ void formatsCompleteArgumentsListWithNames() { var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); var formattedName = format(formatter, 1, arguments(42, "enigma", new Object[] { "foo", 1 })); - assertEquals("someNumber = 42, someString = enigma, someArray = [foo, 1]", formattedName); + assertEquals("someNumber = 42, someString = \"enigma\", someArray = [foo, 1]", formattedName); } @Test @@ -149,7 +153,7 @@ void formatsCompleteArgumentsListWithoutNamesForAggregators() { var formatter = formatter(ARGUMENTS_WITH_NAMES_PLACEHOLDER, "enigma", testMethod); var formattedName = format(formatter, 1, arguments(42, "foo", "bar")); - assertEquals("someNumber = 42, foo, bar", formattedName); + assertEquals("someNumber = 42, \"foo\", \"bar\"", formattedName); } @Test @@ -167,8 +171,8 @@ void formatsEverythingUsingCustomPattern() { var pattern = DISPLAY_NAME_PLACEHOLDER + " " + INDEX_PLACEHOLDER + " :: " + ARGUMENTS_PLACEHOLDER + " :: {1}"; var formatter = formatter(pattern, "enigma"); - assertEquals("enigma 1 :: foo, bar :: bar", format(formatter, 1, arguments("foo", "bar"))); - assertEquals("enigma 2 :: foo, 42 :: 42", format(formatter, 2, arguments("foo", 42))); + assertEquals("enigma 1 :: \"foo\", \"bar\" :: \"bar\"", format(formatter, 1, arguments("foo", "bar"))); + assertEquals("enigma 2 :: \"foo\", 42 :: 42", format(formatter, 2, arguments("foo", 42))); } @Test @@ -176,7 +180,7 @@ void formatDoesNotAlterArgumentsArray() { Object[] actual = { 1, "two", Byte.valueOf("-128"), new Integer[][] { { 2, 4 }, { 3, 9 } } }; var formatter = formatter(ARGUMENTS_PLACEHOLDER, "enigma"); var expected = Arrays.copyOf(actual, actual.length); - assertEquals("1, two, -128, [[2, 4], [3, 9]]", format(formatter, 1, arguments(actual))); + assertEquals("1, \"two\", -128, [[2, 4], [3, 9]]", format(formatter, 1, arguments(actual))); assertArrayEquals(expected, actual); } @@ -201,7 +205,7 @@ void formattingDoesNotFailIfArgumentToStringImplementationReturnsNull() { var formattedName = format(formatter, 1, arguments(new ToStringReturnsNull(), "foo")); - assertThat(formattedName).isEqualTo("null, foo"); + assertThat(formattedName).isEqualTo("null, \"foo\""); } @Test @@ -211,7 +215,7 @@ void formattingDoesNotFailIfArgumentToStringImplementationThrowsAnException() { var formattedName = format(formatter, 1, arguments(new ToStringThrowsException(), "foo")); assertThat(formattedName).startsWith(ToStringThrowsException.class.getName() + "@"); - assertThat(formattedName).endsWith("foo"); + assertThat(formattedName).endsWith("\"foo\""); } @ParameterizedTest(name = "{0}") @@ -241,7 +245,7 @@ void ignoresExcessPlaceholders() { var formattedName = format(formatter, 1, arguments("foo")); - assertThat(formattedName).isEqualTo("foo, {1}"); + assertThat(formattedName).isEqualTo("\"foo\", {1}"); } @Test @@ -250,7 +254,7 @@ void placeholdersCanBeOmitted() { var formattedName = format(formatter, 1, arguments("foo", "bar")); - assertThat(formattedName).isEqualTo("foo"); + assertThat(formattedName).isEqualTo("\"foo\""); } @Test @@ -259,16 +263,16 @@ void placeholdersCanBeSkipped() { var formattedName = format(formatter, 1, arguments("foo", "bar", "baz")); - assertThat(formattedName).isEqualTo("foo, baz"); + assertThat(formattedName).isEqualTo("\"foo\", \"baz\""); } @Test void truncatesArgumentsThatExceedMaxLength() { var formatter = formatter("{arguments}", "display name", 3); - var formattedName = format(formatter, 1, arguments("fo", "foo", "fooo")); + var formattedName = format(formatter, 1, arguments("fo", "foo", "food")); - assertThat(formattedName).isEqualTo("fo, foo, fo…"); + assertThat(formattedName).isEqualTo("\"fo\", \"foo\", \"fo…\""); } @Nested @@ -304,7 +308,7 @@ void argumentSetNameAndArgumentsPlaceholders() { var formattedName = format(formatter, -1, argumentSet("Fruits", "apple", "banana")); - assertThat(formattedName).isEqualTo("Fruits :: apple, banana"); + assertThat(formattedName).isEqualTo("Fruits :: \"apple\", \"banana\""); } @Test @@ -318,7 +322,93 @@ void mixedTypesOfArgumentsImplementationsAndCustomDisplayNamePattern() { var name2 = format(formatter, 2, arguments("apple", "banana")); assertThat(name1).isEqualTo("[1] Mixed Arguments Types :: Fruits"); - assertThat(name2).isEqualTo("[2] Mixed Arguments Types :: fruit1 = apple, fruit2 = banana"); + assertThat(name2).isEqualTo("[2] Mixed Arguments Types :: fruit1 = \"apple\", fruit2 = \"banana\""); + } + + } + + @Nested + class QuotedTextTests { + + @Test + void quotedStrings() { + var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); + + var formattedName = format(formatter, 1, + arguments("Jane Smith", "\\", "\"", "'", "\n", "\r\n", " \t ", "\b", "\f", "\u0007")); + + assertThat(formattedName).isEqualTo(""" + [1] \ + "Jane Smith", \ + "\\\\", \ + "\\"", \ + "'", \ + "\\n", \ + "\\r\\n", \ + " \\t ", \ + "\\b", \ + "\\f", \ + "?"\ + """); + } + + @Test + void quotedCharacters() { + var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); + + var formattedName = format(formatter, 1, + arguments('X', '\\', '\'', '"', '\r', '\n', '\t', '\b', '\f', '\u0007')); + + assertThat(formattedName).isEqualTo(""" + [1] \ + 'X', \ + '\\\\', \ + '\\'', \ + '"', \ + '\\r', \ + '\\n', \ + '\\t', \ + '\\b', \ + '\\f', \ + '?'\ + """); + } + + @Test + void quotedStringsForArgumentsWithNames() { + var testMethod = ParameterizedTestCases.getMethod("processFruit", String.class, int.class); + var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED", testMethod); + + var name1 = format(formatter, 1, arguments("apple", 42)); + var name2 = format(formatter, 2, arguments("banana", 99)); + + assertThat(name1).isEqualTo("[1] fruit = \"apple\", ranking = 42"); + assertThat(name2).isEqualTo("[2] fruit = \"banana\", ranking = 99"); + } + + @Test + void quotedStringsForArgumentsWithNamesAndNamedArguments() { + var testMethod = ParameterizedTestCases.getMethod("processFruit", String.class, int.class); + var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED", testMethod); + + var name1 = format(formatter, 1, arguments(named("Apple", "apple"), 42)); + var name2 = format(formatter, 2, arguments(named("Banana", "banana"), 99)); + + assertThat(name1).isEqualTo("[1] fruit = Apple, ranking = 42"); + assertThat(name2).isEqualTo("[2] fruit = Banana, ranking = 99"); + } + + @Test + void quotedStringsForArgumentsWithNamesAndParameterNameAndArgument() { + var testMethod = ParameterizedTestCases.getMethod("processFruit", String.class, int.class); + var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED", testMethod); + + var name1 = format(formatter, 1, arguments(new ParameterNameAndArgument("FRUIT", "apple"), 42)); + var name2 = format(formatter, 2, arguments(new ParameterNameAndArgument("FRUCHT", "Banane"), 99)); + + // TODO Remove "fruit = " once #4783 has been fixed. + assertThat(name1).isEqualTo("[1] fruit = FRUIT = \"apple\", ranking = 42"); + assertThat(name2).isEqualTo("[2] fruit = FRUCHT = \"Banane\", ranking = 99"); } } @@ -345,7 +435,7 @@ private static ParameterizedInvocationNameFormatter formatter(String pattern, St private static String format(ParameterizedInvocationNameFormatter formatter, int invocationIndex, Arguments arguments) { - return formatter.format(invocationIndex, EvaluatedArgumentSet.allOf(arguments)); + return formatter.format(invocationIndex, EvaluatedArgumentSet.allOf(arguments), true); } @NullUnmarked @@ -382,6 +472,11 @@ void parameterizedTestWithAggregator(int someNumber, @AggregateWith(CustomAggregator.class) String someAggregatedString) { } + @SuppressWarnings("unused") + @ParameterizedTest + void processFruit(String fruit, int ranking) { + } + @SuppressWarnings("unused") @ParameterizedTest void processFruits(String fruit1, String fruit2) { 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 6f751032d63a..c8a2f6449414 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 @@ -233,22 +233,22 @@ private void assertFruitTable(@Nullable String fruit, double rank, TestInfo test if (fruit == null) { assertThat(rank).isEqualTo(0); - assertThat(displayName).matches("\\[(4|5)\\] FRUIT = null, RANK = 0"); + assertThat(displayName).matches("\\[(4|5)\\] FRUIT = null, RANK = \"0\""); return; } switch (fruit) { case "apple" -> { assertThat(rank).isEqualTo(1); - assertThat(displayName).isEqualTo("[1] FRUIT = apple, RANK = 1"); + assertThat(displayName).isEqualTo("[1] FRUIT = \"apple\", RANK = \"1\""); } case "banana" -> { assertThat(rank).isEqualTo(2); - assertThat(displayName).isEqualTo("[2] FRUIT = banana, RANK = 2"); + assertThat(displayName).isEqualTo("[2] FRUIT = \"banana\", RANK = \"2\""); } case "cherry" -> { assertThat(rank).isCloseTo(Math.PI, within(0.0)); - assertThat(displayName).isEqualTo("[3] FRUIT = cherry, RANK = 3.14159265358979323846"); + assertThat(displayName).isEqualTo("[3] FRUIT = \"cherry\", RANK = \"3.14159265358979323846\""); } default -> fail("Unexpected fruit : " + fruit); } @@ -596,13 +596,15 @@ class EmptySourceIntegrationTests { @Test void executesWithEmptySourceForString() { var results = execute("testWithEmptySourceForString", String.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = "))); + results.testEvents().succeeded().assertEventsMatchExactly( + event(test(), displayName("[1] argument = \"\""))); } @Test void executesWithEmptySourceForStringAndTestInfo() { var results = execute("testWithEmptySourceForStringAndTestInfo", String.class, TestInfo.class); - results.testEvents().succeeded().assertEventsMatchExactly(event(test(), displayName("[1] argument = "))); + results.testEvents().succeeded().assertEventsMatchExactly( + event(test(), displayName("[1] argument = \"\""))); } /** @@ -784,7 +786,7 @@ private EngineExecutionResults execute(String methodName, Class... methodPara private void assertNullAndEmptyString(EngineExecutionResults results) { results.testEvents().succeeded().assertEventsMatchExactly(// event(test(), displayName("[1] argument = null")), // - event(test(), displayName("[2] argument = "))// + event(test(), displayName("[2] argument = \"\""))// ); } @@ -1174,6 +1176,7 @@ private EngineExecutionResults execute(String methodName, Class... methodPara @Nested class UnusedArgumentsWithStrictArgumentsCountIntegrationTests { + @Test void failsWithArgumentsSourceProvidingUnusedArguments() { var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, @@ -1427,37 +1430,37 @@ void injectsParametersIntoArgumentsAggregatorConstructor() { static class TestCase { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(TwoSingleStringArgumentsProvider.class) void testWithTwoSingleStringArgumentsProvider(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "foo", "bar" }) void testWithCsvSource(String argument) { fail(argument); } - @ParameterizedTest(name = "{0} and {1}") + @ParameterizedTest(quoteTextArguments = false, name = "{0} and {1}") @CsvSource({ "foo, 23", "bar, 42" }) void testWithCustomName(String argument, int i) { fail(argument + ", " + i); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(shorts = { 1, 2 }) void testWithPrimitiveWideningConversion(double num) { fail("num: " + num); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = { "book 1", "book 2" }) void testWithImplicitGenericConverter(Book book) { fail(book.title); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = { "O", "XXX" }) void testWithExplicitConverter(@ConvertWith(StringLengthConverter.class) int length) { fail("length: " + length); @@ -1469,55 +1472,55 @@ void testWithEmptyName(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(ints = 42) void testWithErroneousConverter(@ConvertWith(ErroneousConverter.class) Object ignored) { fail("this should never be called"); } - @ParameterizedTest(name = "{0,number,#.####}") + @ParameterizedTest(quoteTextArguments = false, name = "{0,number,#.####}") @ValueSource(doubles = Math.PI) void testWithMessageFormat(double argument) { fail(String.valueOf(argument)); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "ab, cd", "ef, gh" }) void testWithAggregator(@AggregateWith(StringAggregator.class) String concatenation) { fail("concatenation: " + concatenation); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = false) void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource(value = { " ab , cd", "ef ,gh" }, ignoreLeadingAndTrailingWhitespace = true) void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "provider/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = false) void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "provider/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = true) void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) void testWithAutoCloseableArgument(AutoCloseableArgument autoCloseable) { assertEquals(0, AutoCloseableArgument.closeCounter); } - @ParameterizedTest(autoCloseArguments = false) + @ParameterizedTest(quoteTextArguments = false, autoCloseArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) void testWithAutoCloseableArgumentButDisabledCleanup(AutoCloseableArgument autoCloseable) { assertEquals(0, AutoCloseableArgument.closeCounter); @@ -1770,7 +1773,7 @@ static class MethodSourceTestCase { @Target(ElementType.METHOD) @Retention(RUNTIME) - @ParameterizedTest(name = "{arguments}") + @ParameterizedTest(quoteTextArguments = false, name = "{arguments}") @MethodSource @interface MethodSourceTest { } @@ -1995,7 +1998,7 @@ static class FieldSourceTestCase { @Target(ElementType.METHOD) @Retention(RUNTIME) - @ParameterizedTest(name = "{arguments}") + @ParameterizedTest(quoteTextArguments = false, name = "{arguments}") @FieldSource @interface FieldSourceTest { } @@ -2159,25 +2162,25 @@ void nonStaticFieldSource(String fruit) { static class UnusedArgumentsTestCase { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) void testWithTwoUnusedStringArgumentsProvider(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "foo, unused1", "bar, unused2" }) void testWithCsvSourceContainingUnusedArguments(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "two-column.csv") void testWithCsvFileSourceContainingUnusedArguments(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("unusedArgumentsProviderMethod") void testWithMethodSourceProvidingUnusedArguments(String argument) { fail(argument); @@ -2187,7 +2190,7 @@ static Stream unusedArgumentsProviderMethod() { return Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @FieldSource("unusedArgumentsProviderField") void testWithFieldSourceProvidingUnusedArguments(String argument) { fail(argument); @@ -2202,13 +2205,13 @@ void testWithStrictArgumentCountValidation(String argument) { fail(argument); } - @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.NONE) + @ParameterizedTest(quoteTextArguments = false, argumentCountValidation = ArgumentCountValidationMode.NONE) @CsvSource({ "foo, unused1" }) void testWithNoneArgumentCountValidation(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "foo, unused1", "bar" }) void testWithCsvSourceContainingDifferentNumbersOfArguments(String argument) { fail(argument); @@ -2262,13 +2265,13 @@ void afterEach(TestInfo testInfo) { lifecycleEvents.add("afterEach:" + testInfo.getDisplayName()); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("providerMethod") void test1(String argument, TestInfo testInfo) { performTest(argument, testInfo); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("providerMethod") void test2(String argument, TestInfo testInfo) { performTest(argument, testInfo); @@ -2290,7 +2293,7 @@ static Stream providerMethod() { static class RepeatableSourcesTestCase { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvFileSource(resources = "two-column.csv") @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") void testWithRepeatableCsvFileSource(String column1, String column2) { @@ -2303,13 +2306,13 @@ void testWithRepeatableCsvFileSource(String column1, String column2) { @interface TwoCsvFileSources { } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoCsvFileSources void testWithRepeatableCsvFileSourceAsMetaAnnotation(String column1, String column2) { fail("%s %s".formatted(column1, column2)); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @CsvSource({ "a" }) @CsvSource({ "b" }) void testWithRepeatableCsvSource(String argument) { @@ -2322,13 +2325,13 @@ void testWithRepeatableCsvSource(String argument) { @interface TwoCsvSources { } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoCsvSources void testWithRepeatableCsvSourceAsMetaAnnotation(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @EnumSource(SmartAction.class) @EnumSource(QuickAction.class) void testWithRepeatableEnumSource(Action argument) { @@ -2341,7 +2344,7 @@ void testWithRepeatableEnumSource(Action argument) { @interface TwoEnumSources { } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoEnumSources void testWithRepeatableEnumSourceAsMetaAnnotation(Action argument) { fail(argument.toString()); @@ -2358,7 +2361,7 @@ private enum QuickAction implements Action { BAR } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("someArgumentsMethodSource") @MethodSource("otherArgumentsMethodSource") void testWithRepeatableMethodSource(String argument) { @@ -2372,7 +2375,7 @@ void testWithRepeatableMethodSource(String argument) { } @SuppressWarnings("JUnitMalformedDeclaration") - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoMethodSources void testWithRepeatableMethodSourceAsMetaAnnotation(String argument) { fail(argument); @@ -2386,7 +2389,7 @@ public static Stream otherArgumentsMethodSource() { return Stream.of(Arguments.of("other")); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @FieldSource("someArgumentsContainer") @FieldSource("otherArgumentsContainer") void testWithRepeatableFieldSource(String argument) { @@ -2399,7 +2402,7 @@ void testWithRepeatableFieldSource(String argument) { @interface TwoFieldSources { } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoFieldSources void testWithRepeatableFieldSourceAsMetaAnnotation(String argument) { fail(argument); @@ -2408,7 +2411,7 @@ void testWithRepeatableFieldSourceAsMetaAnnotation(String argument) { static List someArgumentsContainer = List.of("some"); static List otherArgumentsContainer = List.of("other"); - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "foo") @ValueSource(strings = "bar") void testWithRepeatableValueSource(String argument) { @@ -2421,13 +2424,13 @@ void testWithRepeatableValueSource(String argument) { @interface TwoValueSources { } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoValueSources void testWithRepeatableValueSourceAsMetaAnnotation(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "foo") @ValueSource(strings = "foo") @ValueSource(strings = "foo") @@ -2437,7 +2440,7 @@ void testWithSameRepeatableAnnotationMultipleTimes(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "a") @ValueSource(strings = "b") @CsvSource({ "c" }) @@ -2446,7 +2449,7 @@ void testWithDifferentRepeatableAnnotations(String argument) { fail(argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(TwoSingleStringArgumentsProvider.class) @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) void testWithRepeatableArgumentsSource(String argument) { @@ -2459,7 +2462,7 @@ void testWithRepeatableArgumentsSource(String argument) { @interface TwoArgumentsSources { } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @TwoArgumentsSources void testWithRepeatableArgumentsSourceAsMetaAnnotation(String argument) { fail(argument); @@ -2485,20 +2488,20 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte } }; - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(ArgumentsProviderWithConstructorParameter.class) void argumentsProviderWithConstructorParameter(String argument) { assertEquals("resolved value", argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "value") void argumentConverterWithConstructorParameter( @ConvertWith(ArgumentConverterWithConstructorParameter.class) String argument) { assertEquals("resolved value", argument); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "value") void argumentsAggregatorWithConstructorParameter( @AggregateWith(ArgumentsAggregatorWithConstructorParameter.class) String argument) { @@ -2541,19 +2544,19 @@ protected Object aggregateArguments(ArgumentsAccessor accessor, Class targetT static class ZeroInvocationsTestCase { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("zeroArgumentsProvider") void testThatRequiresInvocations(String argument) { fail("This test should not be executed, because no arguments are provided."); } - @ParameterizedTest(allowZeroInvocations = true) + @ParameterizedTest(quoteTextArguments = false, allowZeroInvocations = true) @MethodSource("zeroArgumentsProvider") void testThatDoesNotRequireInvocations(String argument) { fail("This test should not be executed, because no arguments are provided."); } - @ParameterizedTest(allowZeroInvocations = true) + @ParameterizedTest(quoteTextArguments = false, allowZeroInvocations = true) @SuppressWarnings("JUnitMalformedDeclaration") void testThatHasNoArgumentsSource(String argument) { fail("This test should not be executed, because no arguments source is declared."); @@ -2566,14 +2569,14 @@ public static Stream zeroArgumentsProvider() { static class LocaleConversionTestCase { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "en-US") void testWithBcp47(Locale locale) { assertEquals("en", locale.getLanguage()); assertEquals("US", locale.getCountry()); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ValueSource(strings = "en-US") void testWithIso639(@ConvertWith(Iso639Converter.class) Locale locale) { assertEquals("en-us", locale.getLanguage()); @@ -2677,7 +2680,7 @@ void beforeEach() { fail("beforeEach"); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @ArgumentsSource(AutoCloseableArgumentProvider.class) void test(AutoCloseableArgument autoCloseable) { assertNotNull(autoCloseable); @@ -2698,7 +2701,7 @@ private static Stream getArguments() { return Stream.of("foo", "bar"); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("getArguments") void test(String value) { fail("should not be called: " + value); 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 7fb60bc08063..741806b22d8d 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 @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; @@ -392,7 +393,12 @@ private Stream headersToValues(CsvSource csvSource) { return arguments.map(array -> { String[] strings = new String[array.length]; for (int i = 0; i < array.length; i++) { - strings[i] = String.valueOf(array[i]); + if (array[i] instanceof ParameterNameAndArgument parameterNameAndArgument) { + strings[i] = parameterNameAndArgument.getName() + " = " + parameterNameAndArgument.getPayload(); + } + else { + throw new IllegalStateException("Unexpected argument type: " + array[i].getClass().getName()); + } } return strings; }); 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 ef7c13f4c330..b490ede8c610 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 @@ -33,6 +33,7 @@ import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileArgumentsProvider.InputStreamProvider; +import org.junit.jupiter.params.support.ParameterNameAndArgument; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; @@ -301,7 +302,19 @@ void supportsCsvHeadersInDisplayNames() { .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); - Stream argumentsAsStrings = arguments.map(array -> new String[] { String.valueOf(array[0]) }); + + Stream argumentsAsStrings = arguments.map(array -> { + String[] strings = new String[array.length]; + for (int i = 0; i < array.length; i++) { + if (array[i] instanceof ParameterNameAndArgument parameterNameAndArgument) { + strings[i] = parameterNameAndArgument.getName() + " = " + parameterNameAndArgument.getPayload(); + } + else { + throw new IllegalStateException("Unexpected argument type: " + array[i].getClass().getName()); + } + } + return strings; + }); assertThat(argumentsAsStrings).containsExactly(array("foo = bar"), array("foo = baz"), array("foo = qux"), array("foo = ")); diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt index 87fd06c4b00d..1e33c18000e3 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedInvocationNameFormatterIntegrationTests.kt @@ -21,9 +21,9 @@ class ParameterizedInvocationNameFormatterIntegrationTests { info: TestInfo ) { if (param.equals("foo")) { - assertEquals("[1] param = foo", info.displayName) + assertEquals("[1] param = \"foo\"", info.displayName) } else { - assertEquals("[2] param = bar", info.displayName) + assertEquals("[2] param = \"bar\"", info.displayName) } } @@ -34,9 +34,9 @@ class ParameterizedInvocationNameFormatterIntegrationTests { info: TestInfo ) { if (param.equals("foo")) { - assertEquals("foo", info.displayName) + assertEquals("\"foo\"", info.displayName) } else { - assertEquals("bar", info.displayName) + assertEquals("\"bar\"", info.displayName) } } @@ -56,14 +56,14 @@ class ParameterizedInvocationNameFormatterIntegrationTests { info: TestInfo ) { if (param.equals("foo")) { - assertEquals("displayName and 1st 'argument'(String, TestInfo) - foo", info.displayName) + assertEquals("displayName and 1st 'argument'(String, TestInfo) - \"foo\"", info.displayName) } else { - assertEquals("displayName and 1st 'argument'(String, TestInfo) - bar", info.displayName) + assertEquals("displayName and 1st 'argument'(String, TestInfo) - \"bar\"", info.displayName) } } @ValueSource(strings = ["foo", "bar"]) - @ParameterizedTest(name = "{0} - {displayName}") + @ParameterizedTest(name = "{0} - {displayName}", quoteTextArguments = false) fun `1st 'argument' and displayName`( param: String, info: TestInfo diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt index bc5aa646a35f..78e89943bc64 100644 --- a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt @@ -31,10 +31,14 @@ object DisplayNameTests { @ParameterizedTest @MethodSource("data") fun test( - char: String?, + str: String?, number: Int, info: TestInfo ) { - assertEquals("[$number] char = $char, number = $number", info.displayName) + if (str == null) { + assertEquals("[$number] str = null, number = $number", info.displayName) + } else { + assertEquals("[$number] str = \"$str\", number = $number", info.displayName) + } } } diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java index 8f9c8153041d..956efc990f5d 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java @@ -26,7 +26,7 @@ void addsTwoNumbers() { assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); } - @ParameterizedTest(name = "{0} + {1} = {2}") + @ParameterizedTest(name = "{0} + {1} = {2}", quoteTextArguments = false) @CsvSource({ // "0, 1, 1", // "1, 2, 3", // diff --git a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java index d4ab5f97afcc..9b14b543236d 100644 --- a/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/jupiter-starter/src/test/java/com/example/project/CalculatorTests.java @@ -32,7 +32,7 @@ void addsTwoNumbers() { assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2"); } - @ParameterizedTest(name = "{0} + {1} = {2}") + @ParameterizedTest(name = "{0} + {1} = {2}", quoteTextArguments = false) @CsvSource({ // "0, 1, 1", // "1, 2, 3", // diff --git a/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java b/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java index 7d81da33f214..adbea29ccea0 100644 --- a/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java +++ b/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java @@ -17,7 +17,7 @@ class JupiterParamsIntegration { - @ParameterizedTest(name = "[{index}] argument={0}") + @ParameterizedTest(name = "[{index}] argument={0}", quoteTextArguments = false) @ValueSource(strings = "test") void parameterizedTest(String argument) { assertEquals("test", argument); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java index 0ea59f89f869..1989e2d4e78e 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java @@ -29,7 +29,7 @@ @Order(Integer.MAX_VALUE) class JarContainsManifestFirstTests { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void manifestFirst(String module) throws Exception { var modulePath = MavenRepo.jar(module); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java index fae263aea6f1..099f0ecadb4f 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java @@ -33,7 +33,7 @@ @Order(Integer.MAX_VALUE) class JarDescribeModuleTests { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void describeModule(String module, @FilePrefix("jar") OutputFiles outputFiles) throws Exception { var sourceDirectory = getSourceDirectory(Projects.JAR_DESCRIBE_MODULE); @@ -53,7 +53,7 @@ void describeModule(String module, @FilePrefix("jar") OutputFiles outputFiles) t assertLinesMatch(expectedLines.lines().toList(), result.stdOut().strip().lines().toList()); } - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void packageNamesStartWithNameOfTheModule(String module) { var modulePath = MavenRepo.jar(module); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java index f71b3f14eb12..d982c6f6fe07 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java @@ -36,7 +36,7 @@ @Order(Integer.MAX_VALUE) class ManifestTests { - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") void manifestEntriesAdhereToConventions(String module) throws Exception { var version = Helper.version(); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java index 372f1d2f5fee..c68e03811024 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java @@ -42,7 +42,7 @@ class UnalignedClasspathTests { @ManagedResource MavenRepoProxy mavenRepoProxy; - @ParameterizedTest + @ParameterizedTest(quoteTextArguments = false) @MethodSource("javaVersions") @Execution(SAME_THREAD) void verifyErrorMessageForUnalignedClasspath(JRE jre, Path javaHome, @TempDir Path workspace, diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java index 35c64f2b2be5..6e6c1ab02447 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java @@ -42,7 +42,7 @@ void unsupportedVersion(@FilePrefix("gradle") OutputFiles outputFiles) throws Ex .contains("Unsupported version of junit:junit: 4.11"); } - @ParameterizedTest(name = "{0}") + @ParameterizedTest(name = "{0}", quoteTextArguments = false) @ValueSource(strings = { "4.12", "4.13.2" }) void supportedVersions(String version, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { var result = run(outputFiles, version); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java index ca5d9bc800a4..52436409c163 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java @@ -45,7 +45,7 @@ void unsupportedVersion(@FilePrefix("maven") OutputFiles outputFiles) throws Exc .contains("Tests run: 0, Failures: 0, Errors: 0, Skipped: 0"); } - @ParameterizedTest(name = "{0}") + @ParameterizedTest(name = "{0}", quoteTextArguments = false) @ValueSource(strings = { "4.12", "4.13.2" }) void supportedVersions(String version, @FilePrefix("maven") OutputFiles outputFiles) throws Exception { var result = run(outputFiles, version); From b9618a849014fd25b7489a9557c6261e7ba27765 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:35:27 +0300 Subject: [PATCH 141/162] Support CSV headers with {argumentsWithNames} display name patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, display names using CSV header names did not work properly when using the {argumentsWithNames} display name pattern for parameterized tests. Specifically, both the parameter name and the CSV header name were present in the display name, leading to duplication -- for example: [1] fruit = FRUIT = apple This commit ensures that the parameter name is not displayed when the user has enabled the useHeadersInDisplayName flag in @⁠CsvSource and @⁠CsvFileSource. With this change the above display name is now properly generated as follows (with the superfluous "fruit = " removed). [1] FRUIT = apple See #4716 Closes #4783 --- .../release-notes-6.0.0-RC1.adoc | 6 +++- .../asciidoc/user-guide/writing-tests.adoc | 2 +- .../java/example/ParameterizedTestDemo.java | 2 +- .../ParameterizedInvocationNameFormatter.java | 30 ++++++++++++------- ...meterizedInvocationNameFormatterTests.java | 5 ++-- .../ParameterizedTestIntegrationTests.java | 2 +- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc index c39c0e9621b5..8381557f7970 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC1.adoc @@ -35,7 +35,11 @@ repository on GitHub. [[release-notes-6.0.0-RC1-junit-jupiter-bug-fixes]] ==== Bug Fixes -* ❓ +* CSV headers are now properly supported with the default display name pattern and the + explicit `{argumentsWithNames}` display name pattern for parameterized tests that + utilize the `useHeadersInDisplayName` flag in `@CsvSource` and `@CsvFileSource`. + Specifically, the parameter name is no longer duplicated in the display name when a CSV + header is desired instead. [[release-notes-6.0.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index c699d2a393eb..dffae389793a 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -2236,7 +2236,7 @@ Using a text block, the previous example can be implemented as follows. [source,java,indent=0] ---- -@ParameterizedTest(name = "[{index}] {arguments}") +@ParameterizedTest @CsvSource(useHeadersInDisplayName = true, textBlock = """ FRUIT, RANK apple, 1 diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index f85b9984ce10..25996e330bdc 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -341,7 +341,7 @@ void testWithCsvFileSourceFromFile(String country, int reference) { assertNotEquals(0, reference); } - @ParameterizedTest(name = "[{index}] {arguments}") + @ParameterizedTest @CsvFileSource(resources = "/two-column.csv", useHeadersInDisplayName = true) void testWithCsvFileSourceAndHeaders(String country, int reference) { assertNotNull(country); 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 efff014ebe6f..d977a4dc9b71 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 @@ -163,8 +163,8 @@ private PartialFormatters createPartialFormatters(String displayName, ParameterizedDeclarationContext declarationContext, int argumentMaxLength) { PartialFormatter argumentsWithNamesFormatter = new CachingByArgumentsLengthPartialFormatter( - length -> new MessageFormatPartialFormatter(argumentsWithNamesPattern(length, declarationContext), - argumentMaxLength)); + length -> new MessageFormatPartialFormatter(argumentsPattern(length), argumentMaxLength, true, + declarationContext.getResolverFacade())); PartialFormatter argumentSetNameFormatter = new ArgumentSetNameFormatter( declarationContext.getAnnotationName()); @@ -185,15 +185,6 @@ private PartialFormatters createPartialFormatters(String displayName, return formatters; } - private static String argumentsWithNamesPattern(int length, ParameterizedDeclarationContext declarationContext) { - ResolverFacade resolverFacade = declarationContext.getResolverFacade(); - return IntStream.range(0, length) // - .mapToObj(index -> resolverFacade.getParameterName(index)// - .map(name -> name + " = ").orElse("") // - + "{" + index + "}") // - .collect(joining(", ")); - } - private static String argumentsPattern(int length) { return IntStream.range(0, length) // .mapToObj(index -> "{" + index + "}") // @@ -238,10 +229,19 @@ private static class MessageFormatPartialFormatter implements PartialFormatter { private final MessageFormat messageFormat; private final int argumentMaxLength; + private final boolean generateNameValuePairs; + private final @Nullable ResolverFacade resolverFacade; MessageFormatPartialFormatter(String pattern, int argumentMaxLength) { + this(pattern, argumentMaxLength, false, null); + } + + MessageFormatPartialFormatter(String pattern, int argumentMaxLength, boolean generateNameValuePairs, + @Nullable ResolverFacade resolverFacade) { this.messageFormat = new MessageFormat(pattern); this.argumentMaxLength = argumentMaxLength; + this.generateNameValuePairs = generateNameValuePairs; + this.resolverFacade = resolverFacade; } // synchronized because MessageFormat is not thread-safe @@ -262,9 +262,17 @@ public synchronized void append(ArgumentsContext context, StringBuffer result) { String prefix = ""; if (argument instanceof ParameterNameAndArgument parameterNameAndArgument) { + // This supports the useHeadersInDisplayName attributes in @CsvSource and @CsvFileSource. prefix = parameterNameAndArgument.getName() + " = "; argument = parameterNameAndArgument.getPayload(); } + else if (this.generateNameValuePairs && this.resolverFacade != null) { + Optional parameterName = this.resolverFacade.getParameterName(i); + if (parameterName.isPresent()) { + // This supports the {argumentsWithNames} pattern. + prefix = parameterName.get() + " = "; + } + } if (argument instanceof Character ch) { result[i] = prefix + (quoteTextArguments ? QuoteUtils.quote(ch) : ch); 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 e80fea4a1d2d..e0dca27d6bf1 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 @@ -406,9 +406,8 @@ void quotedStringsForArgumentsWithNamesAndParameterNameAndArgument() { var name1 = format(formatter, 1, arguments(new ParameterNameAndArgument("FRUIT", "apple"), 42)); var name2 = format(formatter, 2, arguments(new ParameterNameAndArgument("FRUCHT", "Banane"), 99)); - // TODO Remove "fruit = " once #4783 has been fixed. - assertThat(name1).isEqualTo("[1] fruit = FRUIT = \"apple\", ranking = 42"); - assertThat(name2).isEqualTo("[2] fruit = FRUCHT = \"Banane\", ranking = 99"); + assertThat(name1).isEqualTo("[1] FRUIT = \"apple\", ranking = 42"); + assertThat(name2).isEqualTo("[2] FRUCHT = \"Banane\", ranking = 99"); } } 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 c8a2f6449414..6eea54d48e21 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 @@ -1264,7 +1264,7 @@ void executesWithRepeatableCsvFileSource(String methodName) { results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] column1 = foo, column2 = 1"), finishedWithFailure(message("foo 1")))) // - .haveExactly(1, event(test(), displayName("[5] column1 = FRUIT = apple, column2 = RANK = 1"), + .haveExactly(1, event(test(), displayName("[5] FRUIT = apple, RANK = 1"), finishedWithFailure(message("apple 1")))); } From c8000fc274f264130f679f3532c5eae3e079088a Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 30 Jul 2025 20:27:16 +0300 Subject: [PATCH 142/162] Cache generated {arguments} patterns for parameterized tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior to this commit, {arguments} patterns were generated for every @⁠ParameterizedTest and @⁠ParameterizedClass, which could result in generation of the same pattern strings hundreds of times per test suite. Specifically, without caching, JUnit Jupiter generated strings like "{0}, {1}", "{0}, {1}, {2}", "{0}, {1}, {2}, {3}", etc. each time such a pattern was needed. To prevent regeneration of identical patterns, this commit modifies ParameterizedInvocationNameFormatter so that it caches each generated pattern in a map, keyed by the number of arguments. Prior to this commit, we generated {arguments} patterns 330 times when running JupiterTestSuite. Whereas, with caching in place we now only generate {arguments} patterns 10 times for the same test suite. Closes #4804 --- .../ParameterizedInvocationNameFormatter.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) 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 d977a4dc9b71..d891ec607e33 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 @@ -11,7 +11,6 @@ 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; import static org.junit.jupiter.params.ParameterizedInvocationConstants.ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; @@ -30,10 +29,10 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; -import java.util.stream.IntStream; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.ExtensionConfigurationException; @@ -48,6 +47,12 @@ */ class ParameterizedInvocationNameFormatter { + /** + * Global cache for {arguments} pattern strings, keyed by the number of arguments. + * @since 6.0 + */ + private static final Map argumentsPatternCache = new ConcurrentHashMap<>(8); + static final String DEFAULT_DISPLAY_NAME = "{default_display_name}"; static final String DEFAULT_DISPLAY_NAME_PATTERN = "[" + INDEX_PLACEHOLDER + "] " + ARGUMENT_SET_NAME_OR_ARGUMENTS_WITH_NAMES_PLACEHOLDER; @@ -186,9 +191,14 @@ private PartialFormatters createPartialFormatters(String displayName, } private static String argumentsPattern(int length) { - return IntStream.range(0, length) // - .mapToObj(index -> "{" + index + "}") // - .collect(joining(", ")); + return argumentsPatternCache.computeIfAbsent(length, // + key -> { + StringJoiner sj = new StringJoiner(", "); + for (int i = 0; i < length; i++) { + sj.add("{" + i + "}"); + } + return sj.toString(); + }); } private record PlaceholderPosition(int index, String placeholder) { From 43b87adb53c812885d70c632f4e586bc871a87c2 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:40:12 +0300 Subject: [PATCH 143/162] Fall back to getTypeName() in AssertionUtils.getCanonicalName() See #4806 --- .../src/main/java/org/junit/jupiter/api/AssertionUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 fb8f052bf2b5..f5dae75d401f 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 @@ -64,11 +64,11 @@ static void fail(Supplier<@Nullable String> messageSupplier) { static String getCanonicalName(Class clazz) { try { String canonicalName = clazz.getCanonicalName(); - return (canonicalName != null ? canonicalName : clazz.getName()); + return (canonicalName != null ? canonicalName : clazz.getTypeName()); } catch (Throwable t) { UnrecoverableExceptions.rethrowIfUnrecoverable(t); - return clazz.getName(); + return clazz.getTypeName(); } } From 99380e033e5afcb4dd5fab67b87184055ab2b662 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:09:45 +0300 Subject: [PATCH 144/162] Use canonical type names in conversion logging and error messages Closes #4806 --- .../execution/ParameterResolutionUtils.java | 6 +-- .../converter/TypedArgumentConverter.java | 4 +- .../ParameterResolutionUtilsTests.java | 21 +++++++++ .../TypedArgumentConverterTests.java | 46 +++++++++++++++++++ 4 files changed, 72 insertions(+), 5 deletions(-) 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 817ce5622c5a..79f1bc8b6c77 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 @@ -157,7 +157,7 @@ public class ParameterResolutionUtils { logger.trace( () -> "ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] in %s [%s].".formatted( - resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), + resolver.getClass().getName(), (value != null ? value.getClass().getTypeName() : null), parameterContext.getParameter(), asLabel(executable), executable.toGenericString())); return value; @@ -198,8 +198,8 @@ private static void validateResolvedType(Parameter parameter, @Nullable Object v message = """ ParameterResolver [%s] resolved a value of type [%s] for parameter [%s] \ in %s [%s], but a value assignment compatible with [%s] is required.""".formatted( - resolver.getClass().getName(), (value != null ? value.getClass().getName() : null), parameter, - asLabel(executable), executable.toGenericString(), type.getName()); + resolver.getClass().getName(), (value != null ? value.getClass().getTypeName() : null), parameter, + asLabel(executable), executable.toGenericString(), type.getTypeName()); } throw new ParameterResolutionException(message); 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 b2dda27b4639..37ad452ac19d 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 @@ -68,12 +68,12 @@ private T convert(@Nullable Object source, Class actualTargetType) { } if (!this.sourceType.isInstance(source)) { String message = "%s cannot convert objects of type [%s]. Only source objects of type [%s] are supported.".formatted( - getClass().getSimpleName(), source.getClass().getName(), this.sourceType.getName()); + getClass().getSimpleName(), source.getClass().getTypeName(), this.sourceType.getTypeName()); throw new ArgumentConversionException(message); } if (!ReflectionUtils.isAssignableTo(this.targetType, actualTargetType)) { String message = "%s cannot convert to type [%s]. Only target type [%s] is supported.".formatted( - getClass().getSimpleName(), actualTargetType.getName(), this.targetType.getName()); + getClass().getSimpleName(), actualTargetType.getTypeName(), this.targetType.getTypeName()); throw new ArgumentConversionException(message); } return convert(this.sourceType.cast(source)); 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 3e2f0e297d6f..de3301e61ef9 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 @@ -12,6 +12,7 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; @@ -250,6 +251,20 @@ void reportTypeMismatchBetweenParameterAndResolvedParameter() { // @formatter:on } + @Test + void reportTypeMismatchBetweenParameterAndResolvedParameterWithArrayTypes() { + testMethodWithASingleStringArrayParameter(); + thereIsAParameterResolverThatResolvesTheParameterTo(new int[][] {}); + + assertThatExceptionOfType(ParameterResolutionException.class)// + .isThrownBy(this::resolveMethodParameters)// + .withMessageContaining(// + "resolved a value of type [int[][]] for parameter [java.lang.String[]", // + "in method", // + "but a value assignment compatible with [java.lang.String[]] is required." // + ); + } + @Test void wrapAllExceptionsThrownDuringParameterResolutionIntoAParameterResolutionException() { anyTestMethodWithAtLeastOneParameter(); @@ -318,6 +333,10 @@ private void testMethodWithASingleStringParameter() { testMethodWith("singleStringParameter", String.class); } + private void testMethodWithASingleStringArrayParameter() { + testMethodWith("singleStringArrayParameter", String[].class); + } + private void testMethodWithASinglePrimitiveIntParameter() { testMethodWith("primitiveParameterInt", int.class); } @@ -413,6 +432,8 @@ interface MethodSource { void singleStringParameter(String parameter); + void singleStringArrayParameter(String[] parameter); + void primitiveParameterInt(int parameter); void multipleParameters(String first, Integer second, Double third); 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 dc93874d7d32..d6f72cc47ee1 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 @@ -83,6 +83,26 @@ void sourceTypeMismatch() { + "Only source objects of type [java.lang.String] are supported."); } + @Test + void sourceTypeMismatchForArrayType() { + Parameter parameter = findParameterOfMethod("stringToByteArray", Byte[].class); + ParameterContext parameterContext = parameterContext(parameter); + assertThatExceptionOfType(ArgumentConversionException.class)// + .isThrownBy(() -> this.converter.convert(new String[][] {}, parameterContext))// + .withMessage("StringLengthArgumentConverter cannot convert objects of type [java.lang.String[][]]. " + + "Only source objects of type [java.lang.String] are supported."); + } + + @Test + void sourceTypeMismatchForPrimitiveArrayType() { + Parameter parameter = findParameterOfMethod("stringToByteArray", Byte[].class); + ParameterContext parameterContext = parameterContext(parameter); + assertThatExceptionOfType(ArgumentConversionException.class)// + .isThrownBy(() -> this.converter.convert(new byte[0], parameterContext))// + .withMessage("StringLengthArgumentConverter cannot convert objects of type [byte[]]. " + + "Only source objects of type [java.lang.String] are supported."); + } + @Test void targetTypeMismatch() { Parameter parameter = findParameterOfMethod("stringToBoolean", Boolean.class); @@ -93,6 +113,26 @@ void targetTypeMismatch() { + "Only target type [java.lang.Integer] is supported."); } + @Test + void targetTypeMismatchForArrayType() { + Parameter parameter = findParameterOfMethod("stringToByteArray", Byte[].class); + ParameterContext parameterContext = parameterContext(parameter); + assertThatExceptionOfType(ArgumentConversionException.class)// + .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// + .withMessage("StringLengthArgumentConverter cannot convert to type [java.lang.Byte[]]. " + + "Only target type [java.lang.Integer] is supported."); + } + + @Test + void targetTypeMismatchForPrimitiveArrayType() { + Parameter parameter = findParameterOfMethod("stringToPrimitiveByteArray", byte[].class); + ParameterContext parameterContext = parameterContext(parameter); + assertThatExceptionOfType(ArgumentConversionException.class)// + .isThrownBy(() -> this.converter.convert("enigma", parameterContext))// + .withMessage("StringLengthArgumentConverter cannot convert to type [byte[]]. " + + "Only target type [java.lang.Integer] is supported."); + } + private ParameterContext parameterContext(Parameter parameter) { ParameterContext parameterContext = mock(); when(parameterContext.getParameter()).thenReturn(parameter); @@ -107,6 +147,12 @@ private Parameter findParameterOfMethod(String methodName, Class... parameter void stringToBoolean(Boolean b) { } + void stringToByteArray(Byte[] array) { + } + + void stringToPrimitiveByteArray(byte[] array) { + } + } /** From c613755edd4f14950286edb709b517fe2b2b33db Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:49:24 +0300 Subject: [PATCH 145/162] Convert quoted text tests to parameterized tests --- ...meterizedInvocationNameFormatterTests.java | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) 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 e0dca27d6bf1..b6e798196ae6 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 @@ -330,48 +330,47 @@ void mixedTypesOfArgumentsImplementationsAndCustomDisplayNamePattern() { @Nested class QuotedTextTests { - @Test - void quotedStrings() { + @ParameterizedTest + @CsvSource(delimiterString = "->", textBlock = """ + 'Jane Smith' -> 'Jane Smith' + \\ -> \\\\ + " -> \\" + # The following represents a single ' enclosed in ''. + '''' -> '''' + '\n' -> \\n + '\r\n' -> \\r\\n + ' \t ' -> ' \\t ' + '\b' -> \\b + '\f' -> \\f + '\u0007' -> ? + """) + void quotedStrings(String argument, String expected) { var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); - var formattedName = format(formatter, 1, - arguments("Jane Smith", "\\", "\"", "'", "\n", "\r\n", " \t ", "\b", "\f", "\u0007")); - - assertThat(formattedName).isEqualTo(""" - [1] \ - "Jane Smith", \ - "\\\\", \ - "\\"", \ - "'", \ - "\\n", \ - "\\r\\n", \ - " \\t ", \ - "\\b", \ - "\\f", \ - "?"\ - """); + var formattedName = format(formatter, 1, arguments(argument)); + assertThat(formattedName).isEqualTo("[1] " + '"' + expected + '"'); } - @Test - void quotedCharacters() { + @ParameterizedTest + @CsvSource(quoteCharacter = '"', delimiterString = "->", textBlock = """ + X -> X + \\ -> \\\\ + ' -> \\' + # The following represents a single " enclosed in "". The escaping is + # necessary, because three " characters in a row close the text block. + \"""\" -> \"""\" + "\n" -> \\n + "\r" -> \\r + "\t" -> \\t + "\b" -> \\b + "\f" -> \\f + "\u0007" -> ? + """) + void quotedCharacters(char argument, String expected) { var formatter = formatter(DEFAULT_DISPLAY_NAME, "IGNORED"); - var formattedName = format(formatter, 1, - arguments('X', '\\', '\'', '"', '\r', '\n', '\t', '\b', '\f', '\u0007')); - - assertThat(formattedName).isEqualTo(""" - [1] \ - 'X', \ - '\\\\', \ - '\\'', \ - '"', \ - '\\r', \ - '\\n', \ - '\\t', \ - '\\b', \ - '\\f', \ - '?'\ - """); + var formattedName = format(formatter, 1, arguments(argument)); + assertThat(formattedName).isEqualTo("[1] " + "'" + expected + "'"); } @Test From 2c03c92a5ab03dc91b76b050d4adabecfd1f0e76 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:35:11 +0300 Subject: [PATCH 146/162] Fix wording in Javadoc for StringToObjectConverter --- .../support/conversion/StringToObjectConverter.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 6c3bfffae363..04f24feba332 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 @@ -30,8 +30,8 @@ interface StringToObjectConverter { * guaranteed to be a wrapper type for primitives — for example, * {@link Integer} instead of {@code int}). * - *

    This method will only be invoked in {@link #canConvertTo(Class)} - * returned {@code true} for the same target type. + *

    This method will only be invoked if {@link #canConvertTo(Class)} + * returns {@code true} for the same target type. */ @Nullable Object convert(String source, Class targetType) throws Exception; @@ -41,8 +41,8 @@ interface StringToObjectConverter { * guaranteed to be a wrapper type for primitives — for example, * {@link Integer} instead of {@code int}). * - *

    This method will only be invoked in {@link #canConvertTo(Class)} - * returned {@code true} for the same target type. + *

    This method will only be invoked if {@link #canConvertTo(Class)} + * returns {@code true} for the same target type. * *

    The default implementation simply delegates to {@link #convert(String, Class)}. * Can be overridden by concrete implementations of this interface that need From 925f1055e9ceedb033b6946501d0933ef406bb84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 16:42:40 +0000 Subject: [PATCH 147/162] Update dependency gradle to v9.0.0 --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a78b3dff983b..3e781fbad9c7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=19ce31d8a4f2e59a99931cc13834c70c0e502804851c0640f31a1af9a7d5b003 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-rc-3-bin.zip +distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 1b102a8b26bcda881c5a7f2f01e700fab658e585 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:03:03 +0000 Subject: [PATCH 148/162] Update github/codeql-action action to v3.29.7 (#4802) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ba464b02f0a9..86730ed36984 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.7 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -53,6 +53,6 @@ jobs: -Dscan.tag.CodeQL \ classes - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.7 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 48d98f5cfd73..eb99262f85d7 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.7 with: sarif_file: results.sarif From f0d1f1dea6302768d386a5ed31d43ead07110583 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:30:04 +0300 Subject: [PATCH 149/162] Polishing --- .../jupiter/params/provider/CsvReaderFactory.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java index 00f6c6de2ddf..3a1bc58ddf04 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvReaderFactory.java @@ -149,13 +149,15 @@ private static CsvCallbackHandler createCallbackHandler(Str record DefaultFieldModifier(String emptyValue, Set nullValues, boolean ignoreLeadingAndTrailingWhitespaces) implements FieldModifier { + /** - * Represents a {@code null} value and serves as a workaround - * since FastCSV does not allow the modified field value to be {@code null}. - *

    - * The marker is generated with a unique ID to ensure it cannot conflict with actual CSV content. + * Represents a {@code null} value and serves as a workaround since FastCSV + * does not allow the modified field value to be {@code null}. + * + *

    The marker is generated with a unique ID to ensure it cannot conflict + * with actual CSV content. */ - static final String NULL_MARKER = "".formatted(UUID.randomUUID()); + static final String NULL_MARKER = "".formatted(UUID.randomUUID()); @Override public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean quoted, String field) { From 24b3c407e9a3640cf3003b7241e1e1378c7c6e89 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:30:28 +0300 Subject: [PATCH 150/162] Improve trimsSpacesUsingStringTrim() test See commit 2a52a0643f See #3824 --- .../provider/CsvArgumentsProviderTests.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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 741806b22d8d..2b92d620cfc9 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 @@ -131,11 +131,25 @@ void trimsTrailingSpaces() { void trimsSpacesUsingStringTrim() { // \u0000 (null) removed by trim(), preserved by strip() // \u00A0 (non-breaking space) preserved by trim(), removed by strip() - var annotation = csvSource().lines("\u0000foo,\u00A0bar", "\u0000' foo',\u00A0' bar'").build(); + var annotation = csvSource().lines( + // Unquoted + "\u0000, \u0000foo\u0000, \u00A0bar\u00A0", + // Quoted + "'\u0000', '\u0000 foo \u0000', ' \u00A0bar\u0000'", + // Mixed + "\u0000'\u0000 foo', \u00A0' bar\u0000'"// + ).build(); var arguments = provideArguments(annotation); - assertThat(arguments).containsExactly(array("foo", "\u00A0bar"), array(" foo", "\u00A0' bar'")); + assertThat(arguments).containsExactly( + // Unquoted + array("", "foo", "\u00A0bar\u00A0"), + // Quoted + array("\u0000", "\u0000 foo \u0000", " \u00A0bar\u0000"), + // Mixed + array("\u0000 foo", "\u00A0' bar\u0000'")// + ); } @Test From 2c0cbd9fca1356b06fa2c717046be74d2d921b6e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:12:24 +0000 Subject: [PATCH 151/162] Update gradle/actions action to v4.4.2 (#4811) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/actions/run-gradle/action.yml | 2 +- .github/workflows/gradle-dependency-submission.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 06b1affcf7ff..9e1022b7fb1a 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -17,7 +17,7 @@ runs: distribution: temurin java-version: 24 check-latest: true - - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-encryption-key: ${{ inputs.encryptionKey }} - shell: bash diff --git a/.github/workflows/gradle-dependency-submission.yml b/.github/workflows/gradle-dependency-submission.yml index 7f3831fa0390..591846c4d613 100644 --- a/.github/workflows/gradle-dependency-submission.yml +++ b/.github/workflows/gradle-dependency-submission.yml @@ -28,4 +28,4 @@ jobs: java-version: 24 check-latest: true - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/dependency-submission@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 From 0ff61103f5fcccad088b6de3b1a71d5963f66528 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:10:52 +0000 Subject: [PATCH 152/162] Update plugin shadow to v9.0.0 (#4807) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 562dba8db4cd..e81aaff81fab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -104,5 +104,5 @@ jreleaser = { id = "org.jreleaser", version = "1.19.0" } kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } -shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc2" } +shadow = { id = "com.gradleup.shadow", version = "9.0.0" } spotless = { id = "com.diffplug.spotless", version = "7.2.1" } From 83b973cede3d39ed7fbf948bdcee88e4cff30ae7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 00:23:45 +0000 Subject: [PATCH 153/162] Update dependency com.puppycrawl.tools:checkstyle to v11 (#4816) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e81aaff81fab..35bfca22ddc3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ asciidoctorj-pdf = "2.3.19" asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.27.3" bnd = "7.1.0" -checkstyle = "10.26.1" +checkstyle = "11.0.0" eclipse = "4.36.0" jackson = "2.19.2" jacoco = "0.8.13" From 1235504dc486556158292ea4bb5eee63c399f62d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:38:02 +0000 Subject: [PATCH 154/162] Update github/codeql-action action to v3.29.8 (#4821) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 86730ed36984..c2d50cd5b5d5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.7 + uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -53,6 +53,6 @@ jobs: -Dscan.tag.CodeQL \ classes - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.7 + uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index eb99262f85d7..968a17c9a399 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.7 + uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: sarif_file: results.sarif From 18315f15b8ebdca0cec61d86738677ff55ac2894 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:38:32 +0000 Subject: [PATCH 155/162] Update dependency com.uber.nullaway:nullaway to v0.12.8 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35bfca22ddc3..0f888e2f87d6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -59,7 +59,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" } +nullaway = { module = "com.uber.nullaway:nullaway", version = "0.12.8" } 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" } From eb5d2bbdb01d88ea26f3da03680e94d9c67d56ed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:38:09 +0000 Subject: [PATCH 156/162] Update dependency com.google.errorprone:error_prone_core to v2.41.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0f888e2f87d6..54536f54b6d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,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.181" } commons-io = { module = "commons-io:commons-io", version = "2.20.0" } -errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.40.0" } +errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.41.0" } fastcsv = { module = "de.siegmar:fastcsv", version = "4.0.0" } groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.28" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } From eadf06646c3f29582ba4406184d61e2f08366efa Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 11 Aug 2025 10:43:38 +0200 Subject: [PATCH 157/162] Fix `EffectivelyPrivate` ErrorProne findings --- .../jupiter/engine/extension/MutableExtensionRegistry.java | 2 +- .../jupiter/engine/extension/TimeoutInvocationFactory.java | 2 +- .../main/java/org/junit/jupiter/params/ResolverFacade.java | 2 +- .../java/org/junit/platform/testkit/engine/EngineTestKit.java | 2 +- .../main/java/org/junit/vintage/engine/execution/TestRun.java | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) 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 f151a02fd5c2..e5037f938262 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 @@ -304,7 +304,7 @@ public Optional getExtension() { return extension; } - public Class getTestClass() { + private Class getTestClass() { return testClass; } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java index 2c066ba9e945..3a7aba81c43c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java @@ -54,7 +54,7 @@ private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { @SuppressWarnings({ "deprecation", "try" }) private abstract static class ExecutorResource implements Store.CloseableResource, AutoCloseable { - protected final ScheduledExecutorService executor; + private final ScheduledExecutorService executor; ExecutorResource(ScheduledExecutorService executor) { this.executor = executor; 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 3495015a73bb..c780d07ce1b7 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 @@ -601,7 +601,7 @@ boolean isAggregator() { || isAnnotated(getAnnotatedElement(), AggregateWith.class); } - protected abstract @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, + abstract @Nullable Object resolve(Resolver resolver, ExtensionContext extensionContext, EvaluatedArgumentSet arguments, int invocationIndex, Optional originalParameterContext); } 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 373335beff74..b1e840cb7ff8 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 @@ -493,7 +493,7 @@ public EngineExecutionResults execute() { private static class DisabledOutputDirectoryProvider implements OutputDirectoryProvider { - public static final OutputDirectoryProvider INSTANCE = new DisabledOutputDirectoryProvider(); + private static final OutputDirectoryProvider INSTANCE = new DisabledOutputDirectoryProvider(); private static final String FAILURE_MESSAGE = "Writing outputs is disabled by default when using EngineTestKit. " + "To enable, configure a custom OutputDirectoryProvider via EngineTestKit#outputDirectoryProvider."; 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 9993a73243ec..e60f77928b08 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 @@ -269,11 +269,11 @@ Optional getUnambiguously(Description description) { // @formatter:on } - public void incrementSkippedOrStarted() { + private void incrementSkippedOrStarted() { skippedOrStartedCount++; } - public Optional getNextUnstarted() { + private Optional getNextUnstarted() { if (skippedOrStartedCount < descriptors.size()) { return Optional.of(descriptors.get(skippedOrStartedCount)); } From f3399ca7257041fb537428fa824780bf82cfb223 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Mon, 11 Aug 2025 11:23:34 +0200 Subject: [PATCH 158/162] Add modular compilation test This commit adds an extra compilation using only `javac` and its multi-module compilation feature in order to detect missing directives in JUnit's module descriptors. --------- Co-authored-by: Marc Philipp --- .../platform-tooling-support-tests.gradle.kts | 19 ++++- .../tests/ModularCompilationTests.java | 72 +++++++++++++++++++ .../support/tests/ModularUserGuideTests.java | 1 - 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java 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 c819aab758bc..6bf3a83bcce4 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -1,9 +1,9 @@ import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigurationInternal import junitbuild.extensions.capitalized import junitbuild.extensions.dependencyProject +import junitbuild.extensions.javaModuleName import net.ltgt.gradle.errorprone.errorprone import org.gradle.api.tasks.PathSensitivity.RELATIVE -import org.gradle.jvm.toolchain.JvmVendorSpec.GRAAL_VM import org.gradle.kotlin.dsl.support.listFilesOrdered import java.time.Duration @@ -45,6 +45,8 @@ val mavenDistributionClasspath = configurations.resolvable("mavenDistributionCla extendsFrom(mavenDistribution.get()) } +val modularProjects: List by rootProject + dependencies { implementation(libs.commons.io) { because("moving/deleting directory trees") @@ -63,11 +65,17 @@ dependencies { } thirdPartyJars(libs.assertj) thirdPartyJars(libs.apiguardian) + thirdPartyJars(libs.fastcsv) thirdPartyJars(libs.hamcrest) + thirdPartyJars(libs.jimfs) thirdPartyJars(libs.jspecify) + thirdPartyJars(kotlin("stdlib")) + thirdPartyJars(kotlin("reflect")) + thirdPartyJars(libs.kotlinx.coroutines) thirdPartyJars(libs.opentest4j) + thirdPartyJars(libs.openTestReporting.events) thirdPartyJars(libs.openTestReporting.tooling.spi) - thirdPartyJars(libs.jimfs) + thirdPartyJars(libs.picocli) antJars(platform(projects.junitBom)) antJars(libs.bundles.ant) @@ -130,7 +138,6 @@ val archUnit by testing.suites.registering(JvmTestSuite::class) { } implementation(libs.assertj) runtimeOnly.bundle(libs.bundles.log4j) - val modularProjects: List by rootProject modularProjects.forEach { runtimeOnly(project(it.path)) } @@ -207,6 +214,12 @@ val test by testing.suites.getting(JvmTestSuite::class) { jvmArgumentProviders += JarPath(project, antJarsClasspath.get(), "antJars") jvmArgumentProviders += MavenDistribution(project, unzipMavenDistribution, mavenDistributionDir) + systemProperty("junit.modules", modularProjects.map { it.javaModuleName }.joinToString(",")) + + jvmArgumentProviders += CommandLineArgumentProvider { + modularProjects.map { "-Djunit.moduleSourcePath.${it.javaModuleName}=${it.sourceSets["main"].allJava.sourceDirectories.filter { it.exists() }.asPath}" } + } + inputs.apply { dir("projects").withPathSensitivity(RELATIVE) file("${rootDir}/gradle.properties").withPathSensitivity(RELATIVE) diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java new file mode 100644 index 000000000000..ade65aa6b247 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularCompilationTests.java @@ -0,0 +1,72 @@ +/* + * 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 platform.tooling.support.tests; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; + +import platform.tooling.support.ProcessStarters; +import platform.tooling.support.ThirdPartyJars; + +class ModularCompilationTests { + + @Test + void compileAllJUnitModules(@TempDir Path workspace, @FilePrefix("javac") OutputFiles javacOutputFiles) + throws Exception { + var lib = Files.createDirectories(workspace.resolve("lib")); + ThirdPartyJars.copyAll(lib); + + var moduleNames = Arrays.asList(System.getProperty("junit.modules").split(",")); + + var outputDir = workspace.resolve("classes").toAbsolutePath(); + var processStarter = ProcessStarters.javaCommand("javac") // + .workingDir(workspace) // + .addArguments("-d", outputDir.toString()) // + .addArguments("-Xlint:all", "-Werror") // + .addArguments("-Xlint:-requires-automatic,-requires-transitive-automatic") // JUnit 4 + // external modules + .addArguments("--module-path", lib.toAbsolutePath().toString()); + + // source locations in module-specific form + moduleNames.forEach( + moduleName -> processStarter.addArguments("--module-source-path", moduleSourcePath(moduleName))); + + var result = processStarter + // un-shadow + .addArguments("--add-modules", "info.picocli") // + .addArguments("--add-reads", "org.junit.platform.console=info.picocli") // + .addArguments("--add-modules", "org.opentest4j.reporting.events") // + .addArguments("--add-reads", "org.junit.platform.reporting=org.opentest4j.reporting.events") // + .addArguments("--add-modules", "de.siegmar.fastcsv") // + .addArguments("--add-reads", "org.junit.jupiter.params=de.siegmar.fastcsv") + // modules to compile + .addArguments("--module", String.join(",", moduleNames)) // + .redirectOutput(javacOutputFiles) // + .startAndWait(); + + assertEquals(0, result.exitCode()); + assertThat(outputDir).isNotEmptyDirectory(); + } + + static String moduleSourcePath(String moduleName) { + return "%s=%s".formatted(moduleName, + requireNonNull(System.getProperty("junit.moduleSourcePath." + moduleName))); + } +} 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 a92420a5c6ae..f74c3f9118c0 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 @@ -156,7 +156,6 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, assertLinesMatch(List.of( // "destination", // ">> CLASSES AND JARS >>", // - "lib/opentest4j-.+\\.jar", // "src", // "src/documentation", // "src/documentation/module-info.java" // From e56243ea9834ceb59f12ac7ca46611ad1229f2f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 09:00:00 +0000 Subject: [PATCH 159/162] Update plugin shadow to v9.0.1 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 54536f54b6d4..692ad0b6851a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -104,5 +104,5 @@ jreleaser = { id = "org.jreleaser", version = "1.19.0" } kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.2.0" } nullaway = { id = "net.ltgt.nullaway", version = "2.2.0" } plantuml = { id = "io.freefair.plantuml", version = "8.14" } -shadow = { id = "com.gradleup.shadow", version = "9.0.0" } +shadow = { id = "com.gradleup.shadow", version = "9.0.1" } spotless = { id = "com.diffplug.spotless", version = "7.2.1" } From 8c54ed4a0d5d730700a9cc86a2fde69976ff8144 Mon Sep 17 00:00:00 2001 From: Marc Philipp Date: Mon, 11 Aug 2025 11:11:46 +0200 Subject: [PATCH 160/162] Adjust to changed default of `duplicatesStrategy` in Shadow plugin --- .../junit-platform-console-standalone.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) 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 632c2fe94568..b8228f708462 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -83,7 +83,10 @@ tasks { """) } + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() + failOnDuplicateEntries = true + manifest.apply { inheritFrom(jar.get().manifest) attributes(mapOf( From 7ebd0a778227e8208ac013f69b6fc0b6eab1ac5d Mon Sep 17 00:00:00 2001 From: Stefano Cordio Date: Mon, 11 Aug 2025 12:29:42 +0200 Subject: [PATCH 161/162] Use `DataFlowIssue` as suppression name alias for `NullAway` (#4779) To avoid having to specify both --- .../test/java/example/session/HttpTests.java | 2 +- .../sharedresources/SharedResourceDemo.java | 2 +- .../java/example/timing/TimingExtension.java | 2 +- ...ld.java-nullability-conventions.gradle.kts | 2 + .../api/condition/MethodBasedCondition.java | 2 +- .../jupiter/api/AssertAllAssertionsTests.java | 8 ++-- .../api/AssertLinesMatchAssertionsTests.java | 2 +- .../junit/jupiter/api/DynamicTestTests.java | 12 ++--- .../jupiter/api/FailAssertionsTests.java | 2 +- .../jupiter/api/extension/MediaTypeTests.java | 2 +- .../junit/jupiter/engine/ReportingTests.java | 2 +- .../DefaultJupiterConfigurationTests.java | 2 +- .../descriptor/DisplayNameUtilsTests.java | 2 +- .../descriptor/LauncherStoreFacadeTest.java | 2 +- .../TestInstanceLifecycleUtilsTests.java | 2 +- .../ParameterResolutionUtilsTests.java | 2 +- .../engine/extension/CloseablePathTests.java | 2 +- .../engine/extension/OrderedMethodTests.java | 2 +- .../engine/extension/TempDirectoryTests.java | 4 +- .../extension/TestInstanceFactoryTests.java | 2 +- .../TestReporterParameterResolverTests.java | 2 +- .../TimeoutExceptionFactoryTests.java | 2 +- .../TimeoutInvocationFactoryTests.java | 6 +-- .../DefaultArgumentsAccessorTests.java | 2 +- .../TypedArgumentConverterTests.java | 2 +- ...AnnotationBasedArgumentsProviderTests.java | 2 +- .../MethodArgumentsProviderTests.java | 2 +- .../platform/commons/function/TryTests.java | 4 +- .../support/AnnotationSupportTests.java | 18 ++++---- .../commons/support/ClassSupportTests.java | 2 +- .../commons/support/ModifierSupportTests.java | 18 ++++---- .../support/ReflectionSupportTests.java | 38 +++++++-------- .../DefaultClasspathScannerTests.java | 10 ++-- .../commons/util/AnnotationUtilsTests.java | 16 +++---- .../commons/util/ClassLoaderUtilsTests.java | 4 +- .../commons/util/CollectionUtilsTests.java | 6 +-- .../commons/util/ExceptionUtilsTests.java | 4 +- .../commons/util/FunctionUtilsTests.java | 4 +- .../commons/util/PackageUtilsTests.java | 10 ++-- .../commons/util/ReflectionUtilsTests.java | 38 +++++++-------- .../commons/util/StringUtilsTests.java | 8 ++-- .../commons/util/ToStringBuilderTests.java | 6 +-- .../CompositeTestDescriptorVisitorTests.java | 2 +- .../junit/platform/engine/TestTagTests.java | 4 +- .../junit/platform/engine/UniqueIdTests.java | 4 +- .../discovery/ClassNameFilterTests.java | 4 +- .../discovery/DiscoverySelectorsTests.java | 46 +++++++++---------- .../discovery/PackageNameFilterTests.java | 4 +- .../PrefixedConfigurationParametersTests.java | 2 +- .../support/descriptor/ClassSourceTests.java | 2 +- .../ClasspathResourceSourceTests.java | 2 +- .../descriptor/CompositeTestSourceTests.java | 2 +- .../descriptor/DefaultUriSourceTests.java | 2 +- .../descriptor/FileSystemSourceTests.java | 2 +- .../support/descriptor/MethodSourceTests.java | 6 +-- .../descriptor/PackageSourceTests.java | 4 +- .../TestDescriptorOrderChildrenTests.java | 2 +- ...lHierarchicalTestExecutorServiceTests.java | 2 +- .../NamespacedHierarchicalStoreTests.java | 6 +-- .../platform/launcher/MethodFilterTests.java | 4 +- .../platform/launcher/TagFilterTests.java | 4 +- .../launcher/core/DefaultLauncherTests.java | 4 +- .../launcher/core/LauncherConfigTests.java | 2 +- .../LauncherConfigurationParametersTests.java | 4 +- .../launcher/core/LauncherFactoryTests.java | 2 +- .../launcher/core/ListenerRegistryTests.java | 4 +- .../NestedContainerEventConditionTests.java | 2 +- 67 files changed, 193 insertions(+), 191 deletions(-) diff --git a/documentation/src/test/java/example/session/HttpTests.java b/documentation/src/test/java/example/session/HttpTests.java index ed68c283380f..8198eae1fc68 100644 --- a/documentation/src/test/java/example/session/HttpTests.java +++ b/documentation/src/test/java/example/session/HttpTests.java @@ -50,7 +50,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon } //end::user_guide[] - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { diff --git a/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java b/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java index 285a7f4632c2..82650ddac062 100644 --- a/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java +++ b/documentation/src/test/java/example/sharedresources/SharedResourceDemo.java @@ -24,7 +24,7 @@ class SharedResourceDemo { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Test void runBothCustomEnginesTest() { diff --git a/documentation/src/test/java/example/timing/TimingExtension.java b/documentation/src/test/java/example/timing/TimingExtension.java index f07abe049c3e..b097aacdf6a7 100644 --- a/documentation/src/test/java/example/timing/TimingExtension.java +++ b/documentation/src/test/java/example/timing/TimingExtension.java @@ -41,7 +41,7 @@ public void beforeTestExecution(ExtensionContext context) { } //end::user_guide[] - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") //tag::user_guide[] @Override public void afterTestExecution(ExtensionContext context) { diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts index 87c01e957fe0..f2664ecaf9f8 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-nullability-conventions.gradle.kts @@ -64,6 +64,8 @@ tasks.withType().configureEach { isJSpecifyMode = true customContractAnnotations.add("org.junit.platform.commons.annotation.Contract") checkContracts = true + // FIXME a new gradle-nullaway-plugin version is needed for a proper DSL + checkOptions.put("NullAway:SuppressionNameAliases", "DataFlowIssue") } } } 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 157a75dadd57..f671e37b214d 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 @@ -88,7 +88,7 @@ private boolean invokeConditionMethod(Method method, ExtensionContext context) { return invokeMethod(method, context, testInstance); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") private static boolean invokeMethod(Method method, ExtensionContext context, @Nullable Object testInstance) { if (method.getParameterCount() == 0) { return (boolean) ReflectionSupport.invokeMethod(method, testInstance); 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 08fdcdd65e1c..0bee5bb609be 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 @@ -37,25 +37,25 @@ */ class AssertAllAssertionsTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullExecutableArray() { assertPrecondition("executables array must not be null or empty", () -> assertAll((Executable[]) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullExecutableCollection() { assertPrecondition("executables collection must not be null", () -> assertAll((Collection) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullExecutableStream() { assertPrecondition("executables stream must not be null", () -> assertAll((Stream) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void assertAllWithNullInExecutableArray() { assertPrecondition("individual executables must not be null", () -> assertAll((Executable) null)); 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 4bc5f0c57765..99e195d9b580 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", "DataFlowIssue", "NullAway" }) + @SuppressWarnings({ "unchecked", "rawtypes", "DataFlowIssue" }) 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/DynamicTestTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java index 409940b6212d..094408a7da9e 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 @@ -43,7 +43,7 @@ class DynamicTestTests { private final List<@Nullable String> assertedValues = new ArrayList<>(); - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamFromStreamPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -58,7 +58,7 @@ void streamFromStreamPreconditions() { () -> DynamicTest.stream(Stream.empty(), displayNameGenerator, null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamFromIteratorPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -73,7 +73,7 @@ void streamFromIteratorPreconditions() { () -> DynamicTest.stream(emptyIterator(), displayNameGenerator, null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamFromStreamWithNamesPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -84,7 +84,7 @@ void streamFromStreamWithNamesPreconditions() { assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(Stream.empty(), null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamFromIteratorWithNamesPreconditions() { ThrowingConsumer testExecutor = input -> { @@ -95,14 +95,14 @@ void streamFromIteratorWithNamesPreconditions() { assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(emptyIterator(), null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamFromStreamWithNamedExecutablesPreconditions() { assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream((Stream) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamFromIteratorWithNamedExecutablesPreconditions() { assertThrows(PreconditionViolationException.class, 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 fff673f1bd56..7bfbc55878de 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 @@ -74,7 +74,7 @@ void failWithNullString() { } } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void failWithNullMessageSupplier() { try { 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 7e1bea151e7c..febe7e8386a4 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,7 +45,7 @@ void parseWithInvalidMediaType() { assertEquals("Invalid media type: 'invalid'", exception.getMessage()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void parseWithNullMediaType() { var exception = assertThrows(PreconditionViolationException.class, () -> MediaType.parse(null)); 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 35ae53e9ae89..5e5c2c88ede2 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,7 +115,7 @@ void succeedingTest(TestReporter reporter) { file -> Files.writeString(file, "succeedingTest")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void invalidReportData(TestReporter reporter) { 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 37b134b978de..0b0489f44c37 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 @@ -44,7 +44,7 @@ class DefaultJupiterConfigurationTests { private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getDefaultTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, 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 40fddf7fbd78..2969192304d0 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 @@ -218,7 +218,7 @@ class NestedTestCase { } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") static class NullDisplayNameGenerator implements DisplayNameGenerator { @Override 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 ae6d12135362..32b5f1520506 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,7 +79,7 @@ void returnsNamespaceAwareStore() { assertNotNull(adapter); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void throwsExceptionWhenNamespaceIsNull() { LauncherStoreFacade facade = new LauncherStoreFacade(requestLevelStore); 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 2617a5153215..dd193832bb29 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,7 +48,7 @@ class TestInstanceLifecycleUtilsTests { private static final String KEY = DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, 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 de3301e61ef9..8adb67dd2112 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 @@ -149,7 +149,7 @@ void onlyConsiderParameterResolversThatSupportAParticularParameter() { assertThat(arguments).containsExactly("something"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void passContextInformationToParameterResolverMethods() { anyTestMethodWithAtLeastOneParameter(); 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 6f4a3bfb427b..3c00dbd27824 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 @@ -167,7 +167,7 @@ void factoryReturnsDirectoryOnNonDefaultFileSystemWithPath() throws IOException delete(closeablePath.get()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @DisplayName("fails if the factory returns null") @ParameterizedTest @ElementTypeSource 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 b19ef8e08bbf..6c195553b3b9 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 @@ -744,7 +744,7 @@ public void orderMethods(MethodOrdererContext context) { context.getMethodDescriptors().set(1, createMethodDescriptorImpersonator(method2)); } - @SuppressWarnings({ "unchecked", "DataFlowIssue" }) + @SuppressWarnings("unchecked") static T createMethodDescriptorImpersonator(MethodDescriptor method) { MethodDescriptor stub = new MethodDescriptor() { @Override 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 a96541568836..2ab92e3aabc1 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 @@ -357,7 +357,7 @@ void doesNotSupportCustomDefaultTempDirFactoryNotReturningDirectory() { private static class FactoryNotReturningDirectory implements TempDirFactory { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { return null; @@ -1475,7 +1475,7 @@ void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tem // never called } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") 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 4ebed49d5b07..579ade0aaecd 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,7 +775,7 @@ private static class LegacyInstanceFactory extends AbstractTestInstanceFactory { */ private static class NullTestInstanceFactory implements TestInstanceFactory { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 c6b811a1d684..d40e678dcae7 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,7 +31,7 @@ class TestReporterParameterResolverTests { TestReporterParameterResolver resolver = new TestReporterParameterResolver(); - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void supports() { Parameter parameter1 = findParameterOfMethod("methodWithTestReporterParameter", TestReporter.class); 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 62f5a6f8ceec..175f0e2468d8 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,7 +51,7 @@ void createExceptionWithMethodSignatureTimeoutAndThrowable() { .hasSuppressedException(suppressedException); } - @SuppressWarnings({ "DataFlowIssue", "NullAway", "ThrowableNotThrown" }) + @SuppressWarnings({ "DataFlowIssue", "ThrowableNotThrown" }) @Nested @DisplayName("throws exception when") class ThrowException { 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 946a0344179e..e8e73bcbd8a3 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,7 +61,7 @@ void setUp() { timeoutInvocationFactory = new TimeoutInvocationFactory(store); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("throws exception when null store is provided on create") void shouldThrowExceptionWhenInstantiatingWithNullStore() { @@ -69,7 +69,7 @@ void shouldThrowExceptionWhenInstantiatingWithNullStore() { .hasMessage("store must not be null"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("throws exception when null timeout thread mode is provided on create") void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { @@ -77,7 +77,7 @@ void shouldThrowExceptionWhenNullTimeoutThreadModeIsProvidedWhenCreate() { .hasMessage("thread mode must not be null"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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/params/aggregator/DefaultArgumentsAccessorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java index 4d223f526a76..f9d80d934e8b 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 @@ -30,7 +30,7 @@ */ class DefaultArgumentsAccessorTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void argumentsMustNotBeNull() { assertThrows(PreconditionViolationException.class, () -> defaultArgumentsAccessor(1, (Object[]) null)); 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 d6f72cc47ee1..fbb2f2ee6dd0 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 @@ -48,7 +48,7 @@ class UnitTests { /** * @since 5.8 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertThatExceptionOfType(PreconditionViolationException.class)// 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 4e75630c5d39..7843b678ce20 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,7 +38,7 @@ protected Stream provideArguments( } }; - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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/MethodArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java index 1f64d9dbc047..828038ae1226 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 @@ -773,7 +773,7 @@ private static Object[] array(Object... objects) { return provider.provideArguments(mock(), extensionContext).map(Arguments::get); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") private DefaultExecutableInvoker getExecutableInvoker(ExtensionContext extensionContext) { return new DefaultExecutableInvoker(extensionContext, extensionRegistry); } 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 d7901d7aac7d..9776fa44c306 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 @@ -72,7 +72,7 @@ void failedTriesCanBeTransformed() throws Exception { assertThat(exception.get()).isSameAs(cause); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void successfulTriesCanStoreNull() throws Exception { var success = Try.success(null); @@ -101,7 +101,7 @@ void triesWithSameContentAreEqual() { assertThat(failure).isEqualTo(Try.failure(cause)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 67c36c8a7c12..9f57d47b7e78 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 @@ -35,7 +35,7 @@ */ class AnnotationSupportTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isAnnotatedPreconditions() { var optional = Optional.of(Probe.class); @@ -59,7 +59,7 @@ void isAnnotatedDelegates() { AnnotationSupport.isAnnotated(element, Override.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotationOnElementPreconditions() { var optional = Optional.of(Probe.class); @@ -84,7 +84,7 @@ void findAnnotationOnElementDelegates() { AnnotationSupport.findAnnotation(element, Override.class)); } - @SuppressWarnings({ "deprecation", "DataFlowIssue", "NullAway" }) + @SuppressWarnings({ "deprecation", "DataFlowIssue" }) @Test void findAnnotationOnClassWithSearchModePreconditions() { assertPreconditionViolationException("annotationType", @@ -93,7 +93,7 @@ void findAnnotationOnClassWithSearchModePreconditions() { () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (SearchOption) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotationOnClassWithEnclosingInstanceTypesPreconditions() { assertPreconditionViolationException("enclosingInstanceTypes", @@ -135,7 +135,7 @@ void findAnnotationOnClassWithEnclosingInstanceTypes() { .contains(Probe.class.getDeclaredAnnotation(Tag.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -154,7 +154,7 @@ void findPublicAnnotatedFieldsDelegates() { AnnotationSupport.findPublicAnnotatedFields(Probe.class, Throwable.class, Override.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedMethodsPreconditions() { assertPreconditionViolationException("Class", @@ -199,7 +199,7 @@ void findRepeatableAnnotationsDelegates() throws Throwable { assertEquals(expected.toString(), actual.toString(), "expected equal exception toString representation"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findRepeatableAnnotationsPreconditions() { assertPreconditionViolationException("annotationType", @@ -225,7 +225,7 @@ void findAnnotatedFieldsDelegates() { HierarchyTraversalMode.TOP_DOWN)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -277,7 +277,7 @@ void findAnnotatedFieldValuesForStaticFieldsByType() { .containsExactlyInAnyOrder("s1", "s2"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 98e90a516900..6689bb26d623 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,7 +24,7 @@ */ class ClassSupportTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 1e2605d77e79..46b30d109f61 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,7 +34,7 @@ */ class ModifierSupportTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isPublicPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isPublic((Class) null)); @@ -52,7 +52,7 @@ void isPublicDelegates(Method method) { assertEquals(ReflectionUtils.isPublic(method), ModifierSupport.isPublic(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isPrivatePreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isPrivate((Class) null)); @@ -70,7 +70,7 @@ void isPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isPrivate(method), ModifierSupport.isPrivate(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isNotPrivatePreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotPrivate((Class) null)); @@ -88,7 +88,7 @@ void isNotPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isNotPrivate(method), ModifierSupport.isNotPrivate(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isAbstractPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isAbstract((Class) null)); @@ -106,7 +106,7 @@ void isAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isAbstract(method), ModifierSupport.isAbstract(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isNotAbstractPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotAbstract((Class) null)); @@ -124,7 +124,7 @@ void isNotAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isNotAbstract(method), ModifierSupport.isNotAbstract(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isStaticPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isStatic((Class) null)); @@ -142,7 +142,7 @@ void isStaticDelegates(Method method) { assertEquals(ReflectionUtils.isStatic(method), ModifierSupport.isStatic(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isNotStaticPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isNotStatic((Class) null)); @@ -160,7 +160,7 @@ void isNotStaticDelegates(Method method) { assertEquals(ReflectionUtils.isNotStatic(method), ModifierSupport.isNotStatic(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isFinalPreconditions() { assertPreconditionViolationException("Class", () -> ModifierSupport.isFinal((Class) null)); @@ -178,7 +178,7 @@ void isFinalDelegates(Method method) { assertEquals(ReflectionUtils.isFinal(method), ModifierSupport.isFinal(method)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 4aea6556942f..5ad6b628b6eb 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,7 +56,7 @@ void tryToLoadClassDelegates() { ReflectionSupport.tryToLoadClass("java.nio.Bits")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToLoadClassPreconditions() { assertPreconditionViolationExceptionForString("Class name", () -> ReflectionSupport.tryToLoadClass(null)); @@ -81,7 +81,7 @@ void tryToLoadClassWithExplicitClassLoaderDelegates() { /** * @since 1.10 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToLoadClassWithExplicitClassLoaderPreconditions() { var cl = getClass().getClassLoader(); @@ -110,7 +110,7 @@ List findAllClassesInClasspathRootDelegates() throws Throwable { /** * @since 1.12 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToGetResourcesPreconditions() { assertPreconditionViolationExceptionForString("Resource name", () -> ReflectionSupport.tryToGetResources(null)); @@ -133,7 +133,7 @@ void tryToGetResources() { ReflectionSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -167,7 +167,7 @@ List findAllResourcesInClasspathRootDelegates() throws Throwable { /** * @since 1.11 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -199,7 +199,7 @@ List streamAllResourcesInClasspathRootDelegates() throws Throwable /** * @since 1.11 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamAllResourcesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -216,7 +216,7 @@ void findAllClassesInPackageDelegates() { ReflectionSupport.findAllClassesInPackage("org.junit", allTypes, allNames)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInPackagePreconditions() { assertPreconditionViolationExceptionForString("basePackageName", @@ -241,7 +241,7 @@ void findAllResourcesInPackageDelegates() { /** * @since 1.11 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllResourcesInPackagePreconditions() { assertPreconditionViolationExceptionForString("basePackageName", @@ -264,7 +264,7 @@ void streamAllResourcesInPackageDelegates() { /** * @since 1.11 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamAllResourcesInPackagePreconditions() { assertPreconditionViolationExceptionForString("basePackageName", @@ -279,7 +279,7 @@ void findAllClassesInModuleDelegates() { ReflectionSupport.findAllClassesInModule("org.junit.platform.commons", allTypes, allNames)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, @@ -303,7 +303,7 @@ void findAllResourcesInModuleDelegates() { /** * @since 1.11 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllResourcesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, @@ -325,7 +325,7 @@ void streamAllResourcesInModuleDelegates() { /** * @since 1.11 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void streamAllResourcesInModulePreconditions() { var exception = assertThrows(PreconditionViolationException.class, @@ -341,7 +341,7 @@ void newInstanceDelegates() { ReflectionSupport.newInstance(String.class, "foo")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void newInstancePreconditions() { assertPreconditionViolationException("Class", () -> ReflectionSupport.newInstance(null)); @@ -358,7 +358,7 @@ void invokeMethodDelegates() throws Exception { ReflectionSupport.invokeMethod(method, null, "true")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void invokeMethodPreconditions() throws Exception { assertPreconditionViolationException("Method", () -> ReflectionSupport.invokeMethod(null, null, "true")); @@ -382,7 +382,7 @@ void findFieldsDelegates() { ReflectionSupport.findFields(ReflectionSupportTests.class, allFields, HierarchyTraversalMode.TOP_DOWN)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -408,7 +408,7 @@ void tryToReadFieldValueDelegates() throws Exception { ReflectionSupport.tryToReadFieldValue(instanceField, this)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToReadFieldValuePreconditions() throws Exception { assertPreconditionViolationException("Field", () -> ReflectionSupport.tryToReadFieldValue(null, this)); @@ -430,7 +430,7 @@ void findMethodDelegates() { ReflectionSupport.findMethod(Boolean.class, "valueOf", String.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findMethodPreconditions() { assertPreconditionViolationException("Class", @@ -464,7 +464,7 @@ void findMethodsDelegates() { ReflectionSupport.findMethods(ReflectionSupportTests.class, allMethods, HierarchyTraversalMode.TOP_DOWN)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findMethodsPreconditions() { assertPreconditionViolationException("Class", @@ -485,7 +485,7 @@ void findNestedClassesDelegates() { ReflectionSupport.findNestedClasses(ClassWithNestedClasses.class, ReflectionUtils::isStatic)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findNestedClassesPreconditions() { assertPreconditionViolationException("Class", 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 d79540ddf728..2612c14e2713 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,14 +441,14 @@ void resourcesCanBeRead() throws IOException { } } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void scanForClassesInPackageForNullBasePackage() { assertThrows(PreconditionViolationException.class, () -> classpathScanner.scanForClassesInPackage(null, allClasses)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void scanForResourcesInPackageForNullBasePackage() { assertThrows(PreconditionViolationException.class, @@ -467,7 +467,7 @@ void scanForResourcesInPackageForWhitespaceBasePackage() { () -> classpathScanner.scanForResourcesInPackage(" ", allResources)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void scanForClassesInPackageForNullClassFilter() { assertThrows(PreconditionViolationException.class, @@ -551,7 +551,7 @@ void findAllClassesInClasspathRootWithFilter() throws Exception { assertTrue(classes.contains(DefaultClasspathScannerTests.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAllClassesInClasspathRootForNullRoot() { assertThrows(PreconditionViolationException.class, @@ -564,7 +564,7 @@ void findAllClassesInClasspathRootForNonExistingRoot() { () -> classpathScanner.scanForClassesInClasspathRoot(Path.of("does_not_exist").toUri(), allClasses)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 03bd7938e141..4f7d0541cbd9 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 @@ -335,14 +335,14 @@ private void assertExtensionsFound(Class clazz, String... tags) { () -> "Extensions found for class " + clazz.getName()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedMethodsForNullClass() { assertThrows(PreconditionViolationException.class, () -> findAnnotatedMethods(null, Annotation1.class, TOP_DOWN)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedMethodsForNullAnnotationType() { assertThrows(PreconditionViolationException.class, @@ -423,21 +423,21 @@ void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { // === findAnnotatedFields() =============================================== - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsForNullClass() { assertThrows(PreconditionViolationException.class, () -> findAnnotatedFields(null, Annotation1.class, isStringField, TOP_DOWN)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsForNullAnnotationType() { assertThrows(PreconditionViolationException.class, () -> findAnnotatedFields(ClassWithAnnotatedFields.class, null, isStringField, TOP_DOWN)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findAnnotatedFieldsForNullPredicate() { assertThrows(PreconditionViolationException.class, @@ -534,21 +534,21 @@ void findAnnotatedFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exce // === findPublicAnnotatedFields() ========================================= - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsForNullClass() { assertThrows(PreconditionViolationException.class, () -> findPublicAnnotatedFields(null, String.class, Annotation1.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsForNullFieldType() { assertThrows(PreconditionViolationException.class, () -> findPublicAnnotatedFields(getClass(), null, Annotation1.class)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findPublicAnnotatedFieldsForNullAnnotationType() { assertThrows(PreconditionViolationException.class, 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 562a0fea2499..c8e8b93136fb 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,7 +29,7 @@ */ class ClassLoaderUtilsTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getClassLoaderPreconditions() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -101,7 +101,7 @@ void getDefaultClassLoaderWithNullContextClassLoader() { } } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 b6b5780c1e41..c89768914341 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 @@ -57,7 +57,7 @@ class CollectionUtilsTests { @Nested class OnlyElement { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void nullCollection() { var exception = assertThrows(PreconditionViolationException.class, @@ -90,7 +90,7 @@ void multiElementCollection() { @Nested class FirstElement { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void nullCollection() { var exception = assertThrows(PreconditionViolationException.class, @@ -183,7 +183,7 @@ void isConvertibleToStreamForNull() { assertThat(CollectionUtils.isConvertibleToStream(null)).isFalse(); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void toStreamWithNull() { Exception exception = assertThrows(PreconditionViolationException.class, 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 cc30b382ceac..c3d38f12864a 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 @@ -34,7 +34,7 @@ @SuppressWarnings("ThrowableNotThrown") class ExceptionUtilsTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void throwAsUncheckedExceptionWithNullException() { assertThrows(PreconditionViolationException.class, () -> throwAsUncheckedException(null)); @@ -50,7 +50,7 @@ void throwAsUncheckedExceptionWithUncheckedException() { assertThrows(RuntimeException.class, () -> throwAsUncheckedException(new NumberFormatException())); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 42d9938ff5af..6efdbb7a69f5 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,14 +26,14 @@ */ class FunctionUtilsTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void whereWithNullFunction() { var exception = assertThrows(PreconditionViolationException.class, () -> FunctionUtils.where(null, o -> true)); assertEquals("function must not be null", exception.getMessage()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 de415b7c4467..3f4ed0700d12 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,7 +33,7 @@ */ class PackageUtilsTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullType() { var exception = assertThrows(PreconditionViolationException.class, @@ -41,7 +41,7 @@ void getAttributeWithNullType() { assertEquals("type must not be null", exception.getMessage()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullFunction() { var exception = assertThrows(PreconditionViolationException.class, @@ -49,7 +49,7 @@ void getAttributeWithNullFunction() { assertEquals("function must not be null", exception.getMessage()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithFunctionReturningNullIsEmpty() { assertFalse(PackageUtils.getAttribute(ValueWrapper.class, p -> null).isPresent()); @@ -78,7 +78,7 @@ private Executable isPresent(Function function) { return () -> assertTrue(PackageUtils.getAttribute(ValueWrapper.class, function).isPresent()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getAttributeWithNullTypeAndName() { var exception = assertThrows(PreconditionViolationException.class, @@ -86,7 +86,7 @@ void getAttributeWithNullTypeAndName() { assertEquals("type must not be null", exception.getMessage()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 ea00a40b2412..2423a01b9318 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 @@ -117,7 +117,7 @@ void returnsPrimitiveVoid() throws Exception { assertFalse(ReflectionUtils.returnsPrimitiveVoid(clazz.getDeclaredMethod("methodReturningPrimitive"))); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getAllAssignmentCompatibleClassesWithNullClass() { assertThrows(PreconditionViolationException.class, @@ -132,7 +132,7 @@ void getAllAssignmentCompatibleClasses() { assertTrue(superclasses.stream().allMatch(clazz -> clazz.isAssignableFrom(B.class))); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void newInstance() { // @formatter:off @@ -252,7 +252,7 @@ private static void createDirectories(Path... paths) throws IOException { } } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getDeclaredConstructorPreconditions() { // @formatter:off @@ -551,7 +551,7 @@ static void staticMethod() { @Nested class IsClassAssignableToClassTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isAssignableToForNullSourceType() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -566,7 +566,7 @@ void isAssignableToForPrimitiveSourceType() { .withMessage("source type must not be a primitive type"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isAssignableToForNullTargetType() { assertThatExceptionOfType(PreconditionViolationException.class)// @@ -623,7 +623,7 @@ void isAssignableTo() { @Nested class IsObjectAssignableToClassTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isAssignableToForNullClass() { assertThrows(PreconditionViolationException.class, @@ -689,7 +689,7 @@ void isAssignableToForNullObjectAndPrimitive() { @Nested class MethodInvocationTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void invokeMethodPreconditions() { // @formatter:off @@ -756,7 +756,7 @@ private void privateMethod() { @Nested class ResourceLoadingTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToGetResourcePreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetResources("")); @@ -798,7 +798,7 @@ void tryToGetResourceWhenResourceNotFound() { @Nested class ClassLoadingTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToLoadClassPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToLoadClass(null)); @@ -965,7 +965,7 @@ private static void assertTryToLoadClass(String name, Class type) { @Nested class FullyQualifiedMethodNameTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getFullyQualifiedMethodNamePreconditions() { // @formatter:off @@ -1001,7 +1001,7 @@ void getFullyQualifiedMethodNameForMethodWithMultipleParameters() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void parseFullyQualifiedMethodNamePreconditions() { // @formatter:off @@ -1041,7 +1041,7 @@ void parseFullyQualifiedMethodNameForMethodWithMultipleParameters() { @Nested class NestedClassTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findNestedClassesPreconditions() { // @formatter:off @@ -1051,7 +1051,7 @@ void findNestedClassesPreconditions() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isNestedClassPresentPreconditions() { // @formatter:off @@ -1232,7 +1232,7 @@ static class ClassExtendingClassWithNestedClasses extends ClassWithNestedClasses @Nested class MethodUtilitiesTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToGetMethodPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetMethod(null, null)); @@ -1257,7 +1257,7 @@ void tryToGetMethod() throws Exception { assertThat(ReflectionUtils.tryToGetMethod(Object.class, "clone", int.class).toOptional()).isEmpty(); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void isMethodPresentPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.isMethodPresent(null, m -> true)); @@ -1279,7 +1279,7 @@ void isMethodPresent() { @Nested class FindMethodTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findMethodByParameterTypesPreconditions() { // @formatter:off @@ -1468,7 +1468,7 @@ void methodWithParameterizedMap(Map map) { @Nested class FindMethodsTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void findMethodsPreconditions() { // @formatter:off @@ -1890,7 +1890,7 @@ void tryToReadFieldValueOfNonexistentInstanceField() { () -> tryToReadFieldValue(MyClass.class, "doesNotExist", new MySubClass(42)).get()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void tryToReadFieldValueOfExistingStaticField() throws Exception { assertThat(tryToReadFieldValue(MyClass.class, "staticField", null).get()).isEqualTo(42); @@ -1942,7 +1942,7 @@ void findFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exception { assertThat(fields).containsExactly(nonStaticField); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void readFieldValuesPreconditions() { List fields = new ArrayList<>(); 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 315fdb472500..7881e831c151 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 @@ -50,7 +50,7 @@ void blankness() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void whitespace() { // @formatter:off @@ -74,7 +74,7 @@ void whitespace() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void controlCharacters() { // @formatter:off @@ -98,7 +98,7 @@ void controlCharacters() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void replaceControlCharacters() { assertNull(replaceIsoControlCharacters(null, "")); @@ -112,7 +112,7 @@ void replaceControlCharacters() { assertThrows(PreconditionViolationException.class, () -> replaceIsoControlCharacters("", null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void replaceWhitespaces() { assertNull(replaceWhitespaceCharacters(null, "")); 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 10d268699f55..ec4b984ddd4a 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 @@ -27,19 +27,19 @@ */ class ToStringBuilderTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void withNullObject() { assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Object) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void withNullClass() { assertThrows(PreconditionViolationException.class, () -> new ToStringBuilder((Class) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void appendWithIllegalName() { var builder = new ToStringBuilder(""); 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 675de2bd7002..1e6ff2924a7e 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,7 +22,7 @@ class CompositeTestDescriptorVisitorTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 0a8bf6fae4cf..70c1e4da3339 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,7 +28,7 @@ */ class TestTagTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void validSyntax() { // @formatter:off @@ -66,7 +66,7 @@ void factory() { assertEquals("foo-tag", TestTag.create("\t foo-tag \n").getName()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void factoryPreconditions() { assertSyntaxViolation(null); 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 4a18ec05cf05..5c1fb2b9f7bf 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,7 +123,7 @@ void appendingSegmentInstance() { assertSegment(uniqueId.getSegments().get(2), "t2", "v2"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void appendingNullIsNotAllowed() { var uniqueId = UniqueId.forEngine(ENGINE_ID); @@ -230,7 +230,7 @@ void additionalSegmentMakesItNotEqual() { @Nested class Prefixing { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 158f086fae47..6abf2556458e 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,7 +23,7 @@ */ class ClassNameFilterTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void includeClassNamePatternsChecksPreconditions() { assertThatThrownBy(() -> ClassNameFilter.includeClassNamePatterns((String[]) null)) // @@ -86,7 +86,7 @@ void includeClassNamePatternsWithMultiplePatterns() { + secondRegex + "'"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 1d8c81a4fbd0..0b8dd823168a 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 @@ -69,7 +69,7 @@ class DiscoverySelectorsTests { @Nested class SelectUriTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectUriByName() { assertViolatesPrecondition(() -> selectUri((String) null)); @@ -82,7 +82,7 @@ void selectUriByName() { assertEquals(uri, selector.getUri().toString()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectUriByURI() { assertViolatesPrecondition(() -> selectUri((URI) null)); @@ -108,7 +108,7 @@ void parseUriSelector() { .isEqualTo(URI.create("https://junit.org")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectFileByName() { assertViolatesPrecondition(() -> selectFile((String) null)); @@ -122,7 +122,7 @@ void selectFileByName() { assertEquals(Path.of(path), selector.getPath()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectFileByNameAndPosition() { var filePosition = FilePosition.from(12, 34); @@ -138,7 +138,7 @@ void selectFileByNameAndPosition() { assertEquals(filePosition, selector.getPosition().orElseThrow()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectFileByFileReference() throws Exception { assertViolatesPrecondition(() -> selectFile((File) null)); @@ -155,7 +155,7 @@ void selectFileByFileReference() throws Exception { assertEquals(Path.of(path), selector.getPath()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectFileByFileReferenceAndPosition() throws Exception { var filePosition = FilePosition.from(12, 34); @@ -232,7 +232,7 @@ void parseFileSelectorWithAbsolutePathAndFilePosition() { Optional.of(filePosition)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectDirectoryByName() { assertViolatesPrecondition(() -> selectDirectory((String) null)); @@ -246,7 +246,7 @@ void selectDirectoryByName() { assertEquals(Path.of(path), selector.getPath()); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectDirectoryByFileReference() throws Exception { assertViolatesPrecondition(() -> selectDirectory((File) null)); @@ -294,7 +294,7 @@ void parseDirectorySelectorWithAbsolutePath() { .containsExactly(path, new File(path), Path.of(path)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectClasspathResourcesPreconditions() { assertViolatesPrecondition(() -> selectClasspathResource((String) null)); @@ -341,7 +341,7 @@ void getMissingClasspathResources() { assertViolatesPrecondition(selector::getClasspathResources); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectClasspathResourcesWithFilePosition() { var filePosition = FilePosition.from(12, 34); @@ -432,7 +432,7 @@ void parseModuleByName() { .isEqualTo("java.base"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectModuleByNamePreconditions() { assertViolatesPrecondition(() -> selectModule(null)); @@ -447,7 +447,7 @@ void selectModulesByNames() { assertThat(names).containsExactlyInAnyOrder("b", "a"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectModulesByNamesPreconditions() { assertViolatesPrecondition(() -> selectModules(null)); @@ -547,7 +547,7 @@ void selectClassByNameWithExplicitClassLoader() throws Exception { @Nested class SelectMethodTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(className, methodName)") void selectMethodByClassNameAndMethodNamePreconditions() { @@ -559,7 +559,7 @@ void selectMethodByClassNameAndMethodNamePreconditions() { assertViolatesPrecondition(() -> selectMethod(" ", "method")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypeNames)") void selectMethodByClassNameMethodNameAndParameterTypeNamesPreconditions() { @@ -572,7 +572,7 @@ void selectMethodByClassNameMethodNameAndParameterTypeNamesPreconditions() { assertViolatesPrecondition(() -> selectMethod("TestClass", "method", (String) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(className, methodName, parameterTypes)") void selectMethodByClassNameMethodNameAndParameterTypesPreconditions() { @@ -586,7 +586,7 @@ void selectMethodByClassNameMethodNameAndParameterTypesPreconditions() { assertViolatesPrecondition(() -> selectMethod("TestClass", "method", new Class[] { int.class, null })); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(class, methodName)") void selectMethodByClassAndMethodNamePreconditions() { @@ -596,7 +596,7 @@ void selectMethodByClassAndMethodNamePreconditions() { assertViolatesPrecondition(() -> selectMethod((Class) null, "method")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(class, methodName, parameterTypeNames)") void selectMethodByClassMethodNameAndParameterTypeNamesPreconditions() { @@ -607,7 +607,7 @@ void selectMethodByClassMethodNameAndParameterTypeNamesPreconditions() { assertViolatesPrecondition(() -> selectMethod(testClass(), "method", (String) null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectMethod(class, method)") void selectMethodByClassAndMethodPreconditions() { @@ -1151,7 +1151,7 @@ void selectDoubleNestedClassByClassNames() { assertThat(parseIdentifier(selector)).isEqualTo(selector); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void selectNestedClassPreconditions() { assertViolatesPrecondition(() -> selectNestedClass(null, "ClassName")); @@ -1319,7 +1319,7 @@ void selectDoubleNestedMethodByEnclosingClassNamesAndMethodName() throws Excepti assertThat(parseIdentifier(selector)).isEqualTo(selector); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName)") void selectNestedMethodByEnclosingClassNamesAndMethodNamePreconditions() { @@ -1331,7 +1331,7 @@ void selectNestedMethodByEnclosingClassNamesAndMethodNamePreconditions() { assertViolatesPrecondition(() -> selectNestedMethod(List.of("ClassName"), "ClassName", " ")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypeNames)") void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesPreconditions() { @@ -1348,7 +1348,7 @@ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypeNamesPreco /** * @since 1.10 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test @DisplayName("Preconditions: selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypes)") void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypesPreconditions() { @@ -1367,7 +1367,7 @@ void selectNestedMethodByEnclosingClassNamesMethodNameAndParameterTypesPrecondit /** * @since 1.10 */ - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 fb52e9c9858b..6f227908e454 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,7 +23,7 @@ */ class PackageNameFilterTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void includePackageChecksPreconditions() { assertThatThrownBy(() -> PackageNameFilter.includePackageNames((String[]) null)) // @@ -75,7 +75,7 @@ void includePackageWithMultiplePackages() { + includedPackage2 + "'"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 c2f4fa7f0fb2..dabfee86e9ac 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,7 +37,7 @@ class PrefixedConfigurationParametersTests { @Mock private ConfigurationParameters delegate; - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 e1ee9cd4e0e3..362a9d5bdb96 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,7 +39,7 @@ Stream createSerializableInstances() { ); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 a84a471d9b92..bb36abdccd3f 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,7 +38,7 @@ Stream createSerializableInstances() { return Stream.of(ClasspathResourceSource.from(FOO_RESOURCE)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 f09f0efbcaa3..cb53867cc682 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,7 +37,7 @@ Stream createSerializableInstances() { return Stream.of(CompositeTestSource.from(sources)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 f68fc5e6d8c3..6b63fed65816 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,7 +33,7 @@ Stream createSerializableInstances() { return Stream.of(new DefaultUriSource(URI.create("sample://instance"))); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 9f6723b85e31..802d2c47cda6 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,7 +34,7 @@ Stream createSerializableInstances() { FileSource.from(new File("file.and.position"), FilePosition.from(42, 23))); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 df9106349d16..7f7f2ba4a6b3 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,7 +57,7 @@ void equalsAndHashCodeForMethodSource() throws Exception { assertEqualsAndHashCode(MethodSource.from(method1), MethodSource.from(method1), MethodSource.from(method2)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void instantiatingWithNullNamesShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, () -> MethodSource.from("foo", null)); @@ -76,13 +76,13 @@ void instantiatingWithBlankNamesShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, () -> MethodSource.from(" ", "foo")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void instantiationWithNullMethodShouldThrowPreconditionViolationException() { assertThrows(PreconditionViolationException.class, () -> MethodSource.from(null)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 7b32ab9cd25a..5f55e05647bb 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 @@ -34,7 +34,7 @@ Stream createSerializableInstances() { return Stream.of(PackageSource.from("package.source")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void packageSourceFromNullPackageName() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from((String) null)); @@ -45,7 +45,7 @@ void packageSourceFromEmptyPackageName() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from(" ")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void packageSourceFromNullPackageReference() { assertThrows(PreconditionViolationException.class, () -> PackageSource.from((Package) null)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorOrderChildrenTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorOrderChildrenTests.java index 8d0e8e239dc1..e14f6c92e438 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorOrderChildrenTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/TestDescriptorOrderChildrenTests.java @@ -116,7 +116,7 @@ default void orderChildrenDuplicatesDescriptor() { assertThat(exception).hasMessage("orderer may not add or remove test descriptors"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test default void orderChildrenOrdererReturnsNull() { var testDescriptor = createTestDescriptorWithChildren(); 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 a31f31598a4a..23a94de108e0 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 @@ -168,7 +168,7 @@ static List compatibleLockCombinations() { ); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @ParameterizedTest @MethodSource("compatibleLockCombinations") void canWorkStealTaskWithCompatibleLocks(Set initialResources, 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 571f321f6d84..a239db0cae2e 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 @@ -207,7 +207,7 @@ void getWithTypeSafety() { assertEquals(value, requiredTypeValue); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void getWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; @@ -288,7 +288,7 @@ void computeIfAbsentWithTypeSafety() { assertEquals(value, computedValue); } - @SuppressWarnings({ "DataFlowIssue", "NullAway", "deprecation" }) + @SuppressWarnings({ "DataFlowIssue", "deprecation" }) @Test void getOrComputeIfAbsentWithTypeSafetyAndPrimitiveValueType() { String key = "enigma"; @@ -357,7 +357,7 @@ void removeWithTypeSafety() { assertNull(store.get(namespace, key)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 af47b9fabd3d..14b8d8bd1d3b 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,7 +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" }) + @SuppressWarnings("DataFlowIssue") @Test void includeMethodNamePatternsChecksPreconditions() { assertThatThrownBy(() -> includeMethodNamePatterns((String[]) null)) // @@ -87,7 +87,7 @@ void includeMultipleMethodNamePatterns() { firstRegex, secondRegex)); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 734bbcdb0a34..e27e9d25addb 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 @@ -61,7 +61,7 @@ void includeTagsWithInvalidSyntax() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") private void assertSyntaxViolationForIncludes(@Nullable String tag) { var exception = assertThrows(PreconditionViolationException.class, () -> includeTags(tag)); assertThat(exception).hasMessageStartingWith("Unable to parse tag expression"); @@ -79,7 +79,7 @@ void excludeTagsWithInvalidSyntax() { // @formatter:on } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") 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 bc3ede9085d5..4be05fe60846 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 @@ -134,7 +134,7 @@ void discoverEmptyTestPlanWithEngineWithoutAnyTests() { void discoverTestPlanForEngineThatReturnsNullForItsRootDescriptor() { TestEngine engine = new TestEngineStub("some-engine-id") { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { return null; @@ -155,7 +155,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId void discoverErrorTestDescriptorForEngineThatThrowsInDiscoveryPhase(Class throwableClass) { TestEngine engine = new TestEngineStub("my-engine-id") { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { try { 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 7b3a92d90e5b..0dda349b3b01 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,7 +30,7 @@ */ class LauncherConfigTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 1ade0e0721de..62f2d578994d 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 @@ -60,7 +60,7 @@ void reset() { System.clearProperty(KEY); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void constructorPreconditions() { assertThrows(PreconditionViolationException.class, () -> fromMap(null)); @@ -69,7 +69,7 @@ void constructorPreconditions() { assertThrows(PreconditionViolationException.class, () -> fromMapAndFile(Map.of(), " ")); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @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 18093ec55367..96ce0271adda 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 @@ -63,7 +63,7 @@ */ class LauncherFactoryTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertThrows(PreconditionViolationException.class, () -> LauncherFactory.create(null)); 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 9f7052c2fd06..c60475a6196f 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 @@ -20,7 +20,7 @@ public class ListenerRegistryTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void registerWithNullArray() { var registry = ListenerRegistry.create(List::getFirst); @@ -39,7 +39,7 @@ void registerWithEmptyArray() { assertThat(exception).hasMessageContaining("listeners array must not be null or empty"); } - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void registerWithArrayContainingNullElements() { var registry = ListenerRegistry.create(List::getFirst); 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 dcece1a3a020..a5dab8cf8538 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,7 +41,7 @@ */ class NestedContainerEventConditionTests { - @SuppressWarnings({ "DataFlowIssue", "NullAway" }) + @SuppressWarnings("DataFlowIssue") @Test void preconditions() { assertThatExceptionOfType(PreconditionViolationException.class)// From fc9477249c6288cdc295651daf18eb0102746ed9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:02:49 +0000 Subject: [PATCH 162/162] Update actions/checkout action to v5 --- .github/workflows/codeql.yml | 2 +- .github/workflows/cross-version.yml | 4 ++-- .github/workflows/gradle-dependency-submission.yml | 2 +- .github/workflows/main.yml | 10 +++++----- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/release.yml | 12 ++++++------ .github/workflows/reproducible-build.yml | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c2d50cd5b5d5..29d03a3d24b9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,7 +36,7 @@ jobs: build-mode: none steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml index 04f821a7f109..612596ae12a4 100644 --- a/.github/workflows/cross-version.yml +++ b/.github/workflows/cross-version.yml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Set up Test JDK @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Set up Test JDK diff --git a/.github/workflows/gradle-dependency-submission.yml b/.github/workflows/gradle-dependency-submission.yml index 591846c4d613..db142002afdf 100644 --- a/.github/workflows/gradle-dependency-submission.yml +++ b/.github/workflows/gradle-dependency-submission.yml @@ -18,7 +18,7 @@ jobs: contents: write steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Setup Java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cd2aeff27388..f9bdef4bdbb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Install GraalVM @@ -48,7 +48,7 @@ jobs: runs-on: windows-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Build @@ -60,7 +60,7 @@ jobs: runs-on: macos-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Build @@ -78,7 +78,7 @@ jobs: if: github.event_name == 'push' && github.repository == 'junit-team/junit-framework' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Publish @@ -105,7 +105,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Install Graphviz diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 968a17c9a399..9a081ffbc6f3 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -21,7 +21,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c736d216ba0..05c7b36721a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: id-token: write # required for build provenance attestation steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" @@ -72,13 +72,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" path: junit-framework - name: Check out examples repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: ${{ github.repository_owner }}/junit-examples token: ${{ secrets.JUNIT_BUILDS_GITHUB_TOKEN_EXAMPLES_REPO }} @@ -161,7 +161,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" @@ -183,7 +183,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" @@ -238,7 +238,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out examples repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: ${{ github.repository_owner }}/junit-examples token: ${{ secrets.JUNIT_BUILDS_GITHUB_TOKEN_EXAMPLES_REPO }} diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml index 9b7fa596f125..9fb6967d73eb 100644 --- a/.github/workflows/reproducible-build.yml +++ b/.github/workflows/reproducible-build.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 1 - name: Restore Gradle cache and display toolchains