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 - --- 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 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 817ab007f246..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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + 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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/analyze@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: category: "/language:${{matrix.language}}" 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: diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 18cacbc0cc06..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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: sarif_file: results.sarif 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/README.md b/README.md index f1be8cc97ecc..cd487ca936cb 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ 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) -- 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) +- 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-M2](https://github.com/junit-team/junit-framework/releases/tag/r6.0.0-M2) (July 22, 2025) ## Documentation 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/documentation.gradle.kts b/documentation/documentation.gradle.kts index 4a10e582fcd5..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 @@ -55,6 +54,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) } @@ -539,17 +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"]) - } -} - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitJupiterParams).configurations["shadowedClasspath"]) - } -} diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index 70c71f7cba49..1504f7fc063d 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] @@ -238,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/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index d3e080b7985f..1d5ceb6cabee 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -17,8 +17,12 @@ 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.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.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.2.adoc index ae95787331ba..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 @@ -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 @@ -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 + 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 @@ -33,8 +46,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..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 @@ -1,32 +1,29 @@ [[release-notes-5.13.3]] == 5.13.3 -*Date of Release:* ❓ +*Date of Release:* July 4, 2025 -*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 repository on GitHub. -[[release-notes-5.13.3-junit-platform]] -=== JUnit Platform +[[release-notes-5.13.3-overall-improvements]] +=== Overall Changes -[[release-notes-5.13.3-junit-platform-bug-fixes]] -==== Bug Fixes - -* ❓ +[[release-notes-5.13.3-overall-new-features-and-improvements]] +==== New Features and Improvements -[[release-notes-5.13.3-junit-platform-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes +* 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-new-features-and-improvements]] -==== New Features and Improvements +[[release-notes-5.13.3-junit-platform]] +=== JUnit Platform -* ❓ +No changes. [[release-notes-5.13.3-junit-jupiter]] @@ -35,34 +32,31 @@ repository on GitHub. [[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`. - -[[release-notes-5.13.3-junit-jupiter-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ +* 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-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()`. +* Improve message of discovery issues reported for ineffective `@Order` annotations. [[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. 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..d36e84b344e6 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.4.adoc @@ -0,0 +1,48 @@ +[[release-notes-5.13.4]] +== 5.13.4 + +*Date of Release:* July 21, 2025 + +*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 +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 + +[[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. +* `PackageSource.from(String)` now allows to be constructed with an empty string to + indicate the default package. + + +[[release-notes-5.13.4-junit-jupiter]] +=== JUnit Jupiter + +[[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]] +=== JUnit Vintage + +No changes. 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..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 @@ -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`. @@ -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 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..612f718724a5 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-M2.adoc @@ -0,0 +1,109 @@ +[[release-notes-6.0.0-M2]] +== 6.0.0-M2 + +*Date of Release:* July 22, 2025 + +*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 +repository on GitHub. + + +[[release-notes-6.0.0-M2-junit-platform]] +=== JUnit Platform + +[[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`. +* 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. +* 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 + +* 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 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. +* 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 the `@Suite` test engine. +* 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]] +=== JUnit Jupiter + +[[release-notes-6.0.0-M2-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* Change the return type of the `provideTestTemplateInvocationContexts(ExtensionContext)` + method in the `TestTemplateInvocationContextProvider` interface from + `Stream` to + `Stream`. +* 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. +* 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 + +* Display names for `@ParameterizedClass` and `@ParameterizedTest` now consistently style + name-value pairs for arguments using `name = value` formatting – for example, + `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 + with non-nullable types. + + +[[release-notes-6.0.0-M2-junit-vintage]] +=== JUnit Vintage + +[[release-notes-6.0.0-M2-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* Provide cancellation support for the Vintage test engine. 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/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/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc index be61e66fd51d..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 @@ -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,41 @@ 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` + +[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. + +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/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/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/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 41555bc48620..68aaf52731b6 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 @@ -1216,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]] @@ -1623,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 @@ -1642,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() ✔ .... @@ -2306,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 @@ -2500,12 +2526,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/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/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/plantuml/component-diagram.puml b/documentation/src/plantuml/component-diagram.puml index a0668125eb61..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 } @@ -73,8 +72,6 @@ console ..> reporting launcher ..> engine -jfr ..> launcher - engine ....> opentest4j engine ..> commons @@ -84,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/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/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index a7312f584cc5..dc41e179994d 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[] } @@ -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/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/UsingTheLauncherDemo.java b/documentation/src/test/java/example/UsingTheLauncherDemo.java index ee5ff525c565..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,63 +18,42 @@ 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; +import org.junit.platform.launcher.LauncherExecutionRequest; import org.junit.platform.launcher.LauncherSession; 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; +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; 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 // tag::execution[] - LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() + LauncherDiscoveryRequest discoveryRequest = LauncherDiscoveryRequestBuilder.request() .selectors( selectPackage("com.example.mytests"), selectClass(MyTestClass.class) @@ -94,11 +73,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(); @@ -108,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); @@ -128,8 +107,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)) { @@ -139,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/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/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/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/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/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/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/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.properties b/gradle.properties index 3b591bcde84e..a0e8d72f8201 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 6.0.0-M1 +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 diff --git a/gradle/config/checkstyle/checkstyleMain.xml b/gradle/config/checkstyle/checkstyleMain.xml index ed8dadaaff8d..51a5906293f9 100644 --- a/gradle/config/checkstyle/checkstyleMain.xml +++ b/gradle/config/checkstyle/checkstyleMain.xml @@ -27,6 +27,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/config/checkstyle/checkstyleTest.xml b/gradle/config/checkstyle/checkstyleTest.xml index b795207938ad..f564bd1c4872 100644 --- a/gradle/config/checkstyle/checkstyleTest.xml +++ b/gradle/config/checkstyle/checkstyleTest.xml @@ -19,6 +19,14 @@ + + + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9390d0778395..f7f53670ce89 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,16 +5,15 @@ 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" +jackson = "2.19.2" jacoco = "0.8.13" jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" -ktlint = "1.6.0" -log4j = "2.25.0" -logback = "1.5.18" +ktlint = "1.7.1" +log4j = "2.25.1" opentest4j = "1.3.0" openTestReporting = "0.2.4" snapshotTests = "1.11.0" @@ -33,9 +32,9 @@ 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" } -commons-io = { module = "commons-io:commons-io", version = "2.19.0" } -errorProne-core = { module = "com.google.errorprone:error_prone_core", version = "2.39.0" } +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" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } @@ -43,7 +42,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" } @@ -53,7 +52,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" } @@ -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] @@ -96,18 +94,15 @@ 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" } 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" } -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" } -spotless = { id = "com.diffplug.spotless", version = "7.0.4" } +shadow = { id = "com.gradleup.shadow", version = "9.0.0-rc1" } +spotless = { id = "com.diffplug.spotless", version = "7.2.1" } 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.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.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..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 @@ -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") } } } @@ -31,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") 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..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,21 +8,10 @@ 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") id("junitbuild.jacoco-java-conventions") - id("org.openrewrite.rewrite") -} - -rewrite { - activeRecipe("org.openrewrite.java.migrate.UpgradeToJava17") -} - -dependencies { - rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:latest.release")) - rewrite("org.openrewrite.recipe:rewrite-migrate-java") } val mavenizedProjects: List by rootProject.extra 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..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 { @@ -19,14 +27,43 @@ 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 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 + // classes and methods + "MissingSummary", + ) + error("PackageLocation") + } else { + disableAllChecks = true + } nullaway { - if (java.toolchain.implementation.orNull == JvmImplementation.J9) { + if (onJ9) { disable() } else { enable() } isJSpecifyMode = true + customContractAnnotations.add("org.junit.platform.commons.annotation.Contract") + checkContracts = true } } } 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 """ ) 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 { 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) 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") { 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")) diff --git a/gradle/plugins/publishing/build.gradle.kts b/gradle/plugins/publishing/build.gradle.kts index a52228904a92..99791df232bd 100644 --- a/gradle/plugins/publishing/build.gradle.kts +++ b/gradle/plugins/publishing/build.gradle.kts @@ -9,18 +9,18 @@ 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") } because("Workaround for CVE-2025-4949") } + implementation("org.apache.commons:commons-lang3") { + version { + require("3.18.0") + } + because("Workaround for CVE-2025-48924") + } } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb..8bdaf60c75ab 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3735f265b953..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=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-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. diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index f2592212772c..c0c075f7735f 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -13,17 +13,21 @@ dependencies { api(projects.junitPlatformCommons) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(libs.jspecify) compileOnly(kotlin("stdlib")) testFixturesImplementation(libs.assertj) + testFixturesImplementation(testFixtures(projects.junitPlatformCommons)) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } 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 2660f24f0d87..036ed66a2669 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; @@ -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/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/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/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/AssertTimeoutPreemptively.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java index a2d277de1932..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,29 +76,7 @@ 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/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 90f5837520af..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 @@ -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; @@ -26,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; /** @@ -116,7 +116,8 @@ protected Assertions() { *

See Javadoc for {@link #fail(String)} for an explanation of this method's * generic return type {@code V}. */ - @SuppressWarnings("NullAway") + @Contract(" -> fail") + @SuppressWarnings({ "NullAway", "TypeParameterUnusedInFormals" }) public static V fail() { AssertionUtils.fail(); return null; // appeasing the compiler: this line will never be executed. @@ -136,7 +137,8 @@ public static V fail() { * Stream.of().map(entry -> fail("should not be called")); * } */ - @SuppressWarnings("NullAway") + @Contract("_ -> fail") + @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 +151,8 @@ 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") + @Contract("_, _ -> fail") + @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 +164,8 @@ 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") + @Contract("_ -> fail") + @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 +178,8 @@ 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") + @Contract("_ -> fail") + @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. @@ -185,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); } @@ -193,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); } @@ -216,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); } @@ -233,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); } @@ -241,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); } @@ -249,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); } @@ -281,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); } @@ -289,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); } @@ -297,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); } @@ -306,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); } @@ -314,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); } @@ -322,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); } @@ -1741,7 +1758,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 +1768,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 +1778,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 +1788,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 +1798,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 +1810,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 +1822,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 +1834,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 +1846,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 +1859,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 +1873,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 +1887,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 +1901,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 +1911,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 +1921,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 +1931,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 +1941,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 +1953,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 +1965,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 +1977,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 +1989,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 +2002,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 +2016,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 +2030,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 +2044,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 +2054,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 +2064,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 +2074,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 +2084,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 +2096,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 +2108,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 +2120,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 +2133,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 +2146,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 +2160,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 +2174,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 +2188,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 +2198,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 +2208,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 +2218,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 +2228,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 +2240,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 +2252,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 +2264,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 +2276,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 +2289,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 +2303,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 +2317,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 +2331,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 +2344,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 +2357,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 +2370,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 +2383,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 +2398,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 +2413,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 +2428,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 +2443,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 +2456,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 +2470,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 +2484,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 +2498,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 +2512,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 +2528,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 +2543,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 +2556,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 +2569,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 +2582,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 +2595,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 +2610,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 +2625,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 +2640,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 +2655,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 +2668,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 +2682,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 +2696,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 +2710,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 +2724,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 +2740,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 +2755,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 +2765,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 +2775,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 +2785,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 +2795,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 +2807,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 +2819,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 +2831,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 +2844,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 +2857,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 +2871,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 +2885,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 +2899,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 +2910,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 +2923,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 +3115,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. @@ -3654,36 +3671,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 ---------------------------------------------------- /** @@ -3696,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); } @@ -3712,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); @@ -3729,28 +3718,11 @@ 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) { 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/Assumptions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java index b0619ad3cc32..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,7 +284,9 @@ 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() { throw new TestAbortedException(); } @@ -300,7 +309,9 @@ 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) { throw new TestAbortedException(message); } @@ -316,11 +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/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/RandomOrdererUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java index 16ca5250e539..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 @@ -41,13 +41,15 @@ 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; } }); } + + private RandomOrdererUtils() { + } } 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/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-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-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/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 5b683e43f32b..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 @@ -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; @@ -29,20 +30,26 @@ 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(String) */ - public static ConditionEvaluationResult enabled(String reason) { + public static ConditionEvaluationResult enabled(@Nullable String reason) { return new ConditionEvaluationResult(true, 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(String) */ - public static ConditionEvaluationResult disabled(String reason) { + public static ConditionEvaluationResult disabled(@Nullable String reason) { return new ConditionEvaluationResult(false, reason); } @@ -50,26 +57,36 @@ 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(String) */ @API(status = STABLE, since = "5.7") - public static ConditionEvaluationResult disabled(String reason, String customReason) { + public static ConditionEvaluationResult disabled(@Nullable String reason, @Nullable String customReason) { + if (StringUtils.isBlank(reason)) { + return disabled(customReason); + } 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; private final Optional reason; - private ConditionEvaluationResult(boolean enabled, String reason) { + private ConditionEvaluationResult(boolean enabled, @Nullable String reason) { this.enabled = enabled; - this.reason = Optional.ofNullable(reason); + this.reason = StringUtils.isNotBlank(reason) ? Optional.of(reason.strip()) : Optional.empty(); } /** 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..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}. * @@ -751,7 +870,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/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-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-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-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-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-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/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-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..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 @@ -48,7 +48,10 @@ @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", // + "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); })); } @@ -146,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); } @@ -186,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 @@ -199,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 d0a470aed8ec..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().trim().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(() -> String.format( - "Invalid %s '%s' set via the '%s' configuration parameter. " - + "Falling back to the %s default value.", - 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/config/InstantiatingConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java index 5f2eaf34745b..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; } @@ -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); @@ -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/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/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/ClassTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTemplateTestDescriptor.java index 29fb3c54652d..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); } @@ -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/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java index bb8d5e5427b2..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 @@ -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); } @@ -155,4 +155,7 @@ private static Optional findDisplayNameGenerator(List { 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/MethodSourceSupport.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java index 19b17efb83e4..39b96d9c5bfa 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodSourceSupport.java @@ -64,4 +64,7 @@ static MethodSource from(URI uri) { String[] methodSpec = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName); return MethodSource.from(methodSpec[0], methodSpec[1], methodSpec[2]); } + + private MethodSourceSupport() { + } } 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/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/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/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/DiscoverySelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java index dbe6c9a92a2b..d7385521a4bb 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java @@ -62,4 +62,7 @@ public static void resolveSelectors(EngineDiscoveryRequest request, JupiterEngin resolver.resolve(request, engineDescriptor, issueReporter); } + private DiscoverySelectorResolver() { + } + } 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/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/discovery/predicates/TestClassPredicates.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/TestClassPredicates.java index d34f8a885d68..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 @@ -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; @@ -26,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; @@ -54,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)) // @@ -74,7 +77,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); @@ -83,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) { @@ -123,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."); }); } @@ -140,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(); @@ -150,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/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/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/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/execution/ParameterResolutionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java index 6b8379a56f1f..817ce5622c5a 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()); } @@ -210,4 +210,7 @@ private static String asLabel(Executable executable) { return executable instanceof Constructor ? "constructor" : "method"; } + private ParameterResolutionUtils() { + } + } 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/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/RepeatedTestExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java index 46b91c5e8a77..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(); @@ -68,8 +67,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; @@ -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-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/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/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..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; @@ -96,7 +95,7 @@ class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterR private final JupiterConfiguration configuration; - public TempDirectory(JupiterConfiguration configuration) { + TempDirectory(JupiterConfiguration configuration) { this.configuration = configuration; } @@ -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(); } @@ -439,9 +438,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)); } @@ -526,6 +525,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..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,8 +21,7 @@ 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.Map; import java.util.Optional; @@ -32,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 @@ -46,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() { @@ -124,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()); - 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/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/TimeoutExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java index 4c8a611f3eed..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,8 +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; import java.lang.reflect.AnnotatedElement; @@ -24,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; @@ -32,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 @@ -43,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) { @@ -159,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 requireNonNull(root.getStore(NAMESPACE).getOrComputeIfAbsent(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; } @@ -211,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-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..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,11 +48,11 @@ 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" }) - private static abstract class ExecutorResource implements Store.CloseableResource, AutoCloseable { + private abstract static class ExecutorResource implements Store.CloseableResource, AutoCloseable { protected final ScheduledExecutorService executor; @@ -80,7 +78,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-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java index 6c9eabb3a25e..1e702956e3e7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java @@ -34,4 +34,7 @@ public static ThrowableCollector createThrowableCollector() { return new OpenTest4JAndJUnit4AwareThrowableCollector(); } + private JupiterThrowableCollectorFactory() { + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java index a72d013134f4..02c8ca228ff6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/MethodReflectionUtils.java @@ -44,4 +44,7 @@ public static Type getGenericReturnType(Method method) { } return ReflectionSupport.invokeMethod(method, target, arguments); } + + private MethodReflectionUtils() { + } } diff --git a/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java b/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java index d504f3605d65..2df3b17eb48e 100644 --- a/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java +++ b/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java @@ -85,4 +85,7 @@ public static UniqueId engineId() { return UniqueId.forEngine(JupiterEngineDescriptor.ENGINE_ID); } + private JupiterUniqueIdBuilder() { + } + } 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-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..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 @@ -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,14 +55,14 @@ 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); - 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/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-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..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(); @@ -86,17 +84,16 @@ 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; } } else { return fallback; } - }, ArgumentCountValidationMode.class)); + }, ArgumentCountValidationMode.class); } private static String pluralize(int count, String singular, String plural) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassContext.java index b6002a4ad817..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; @@ -60,14 +59,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 @@ -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); } @@ -146,8 +145,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/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/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/ParameterizedInvocationNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedInvocationNameFormatter.java index 82ac1a6f40a7..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 @@ -57,11 +57,11 @@ 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; - 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())); @@ -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()); @@ -186,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(", ")); } @@ -200,6 +202,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) { } @@ -247,6 +250,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); 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 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)); 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..2510ad41c9d9 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,11 +65,14 @@ 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); } + private ParameterizedTestSpiInstantiator() { + } + } 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..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 @@ -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); @@ -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<>(); @@ -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/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/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/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/converter/DefaultArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java index 46c29d6a9cc0..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.trim().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-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 9a7c4ee47184..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 @@ -103,6 +102,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-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..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 @@ -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) { @@ -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; } @@ -174,4 +174,7 @@ public String modify(long unusedStartingLineNumber, int unusedFieldIdx, boolean } + private CsvReaderFactory() { + } + } 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/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..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; @@ -73,7 +72,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(".")) { @@ -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/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-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java new file mode 100644 index 000000000000..af449e98d524 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Contract.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.annotation; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * Specifies some aspects of the annotated method's behavior to be used by tools + * for data flow analysis. + * + * @since 6.0 + * @see org.jetbrains.annotations.Contract + * @see NullAway custom contract annotations + */ +@Documented +@Target(ElementType.METHOD) +@API(status = INTERNAL, since = "6.0") +public @interface Contract { + + /** + * Contains the contract clauses describing causal relations between call + * arguments and the returned value. + */ + String value(); + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java index 115ccb8e8ef2..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 @@ -249,7 +250,7 @@ public interface Transformer extends Try { + private static final class Success extends Try { private final V value; @@ -327,7 +328,7 @@ public int hashCode() { } } - private static class Failure extends Try { + private static final class Failure extends Try { private final Exception cause; @@ -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/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..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,8 @@ 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) { @@ -93,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); } @@ -112,6 +116,7 @@ public static boolean isAnnotated(@Nullable AnnotatedElement element, Class Optional findAnnotation( @Nullable Optional element, Class annotationType) { @@ -253,6 +258,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/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/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/support/scanning/DefaultClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java index 427b8867d946..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; @@ -87,7 +86,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 +106,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); @@ -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..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(); } @@ -326,7 +329,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) { @@ -381,7 +384,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/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/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/ExceptionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java index 8874905af32a..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,7 +78,8 @@ public static RuntimeException throwAsUncheckedException(Throwable t) { return ExceptionUtils.throwAs(t); } - @SuppressWarnings("unchecked") + @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/KotlinReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java index 926ae651ee99..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 @@ -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,11 +29,13 @@ /** * 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 String DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"; + private static final @Nullable Class kotlinMetadata; private static final @Nullable Class kotlinCoroutineContinuation; private static final boolean kotlinReflectPresent; @@ -56,6 +62,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 +75,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; @@ -112,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 363af7fcffc3..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 @@ -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); } @@ -109,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..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) @@ -307,4 +307,7 @@ private Resource loadResourceUnchecked(String binaryName) { } + private ModuleUtils() { + } + } 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/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index d9bd8e4b4849..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; @@ -113,7 +112,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(); @@ -217,6 +216,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 +232,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 +641,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)); + () -> "Cannot read non-static field [%s] on a null instance.".formatted(field)); - return Try.call(() -> makeAccessible(field).get(instance)); + return Try.<@Nullable Object> call(() -> makeAccessible(field).get(instance)); } /** @@ -693,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); @@ -728,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)); } /** @@ -747,15 +750,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 +769,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); }); } @@ -887,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); } /** @@ -1272,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]; } @@ -1421,6 +1424,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; @@ -1475,7 +1479,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 @@ -1487,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 } @@ -1553,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()))); } /** @@ -1959,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-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..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}, @@ -66,10 +67,12 @@ 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) */ + @Contract("null -> true") public static boolean isBlank(@Nullable String str) { - return (str == null || str.trim().isEmpty()); + return (str == null || str.isBlank()); } /** @@ -80,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); } @@ -92,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); } @@ -106,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); } @@ -118,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); } @@ -132,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); } @@ -240,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); @@ -255,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-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..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 @@ -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); @@ -69,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/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-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 bd39bff687da..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 @@ -35,9 +35,13 @@ 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(); } + private ConsoleUtils() { + } + } 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/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/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..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 @@ -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; @@ -62,7 +64,7 @@ private static Map singleColorPalette() { return colorsToAnsiSequences; } - public ColorPalette(Map overrides) { + ColorPalette(Map overrides) { this(defaultPalette(), false); if (overrides.containsKey(Style.NONE)) { @@ -71,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)); } @@ -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-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..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.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/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-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-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/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-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/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-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/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-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..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 @@ -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,7 @@ 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 +102,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 +175,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/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/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/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/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/UniqueId.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java index c60e716d54ec..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 @@ -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; @@ -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); @@ -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..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; @@ -53,7 +54,7 @@ * @see #getClasspathResourceName() */ @API(status = STABLE, since = "1.0") -public class ClasspathResourceSelector implements DiscoverySelector { +public final class ClasspathResourceSelector implements DiscoverySelector { private final String classpathResourceName; @@ -64,6 +65,8 @@ public 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/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/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/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-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..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 @@ -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; @@ -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 838b92d9ad8e..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 @@ -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; @@ -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 15438b93c4b1..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 @@ -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; @@ -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 a2b3645bd203..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 @@ -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; @@ -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/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..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 @@ -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; @@ -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 1917dd19b168..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 @@ -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; @@ -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 d3c46953a3f9..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 @@ -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; @@ -60,13 +60,16 @@ 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; } /** * 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/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/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/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..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 @@ -16,28 +16,20 @@ 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.lang.Thread.UncaughtExceptionHandler; -import java.lang.reflect.Constructor; +import java.io.Serial; 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; 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 @@ -161,9 +134,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); @@ -224,6 +197,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) { 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..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 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/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/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-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..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 @@ -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; @@ -86,6 +88,11 @@ public ExecutionMode getExecutionMode() { .orElseGet(node::getExecutionMode); } + @Override + public TestDescriptor getTestDescriptor() { + return testDescriptor; + } + @Override public String toString() { return "NodeTestTask [" + testDescriptor + "]"; @@ -99,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(); @@ -116,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(); } @@ -139,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() { @@ -188,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); @@ -230,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/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/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-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/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/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-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/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..474913fc9971 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherExecutionRequest.java @@ -0,0 +1,70 @@ +/* + * 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; +import org.junit.platform.engine.CancellationToken; + +/** + * {@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(); + + /** + * {@return the cancellation token for this execution request} + */ + CancellationToken getCancellationToken(); + +} 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/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/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/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/ClasspathAlignmentChecker.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java index 312fe2519ad7..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 @@ -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" // @@ -86,4 +85,7 @@ static Optional check(LinkageError error, Function { + 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(), + launcherExecutionRequest.getCancellationToken()); } 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, + 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 new file mode 100644 index 000000000000..7992b27ceaa9 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherExecutionRequest.java @@ -0,0 +1,62 @@ +/* + * 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.engine.CancellationToken; +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; + private final CancellationToken cancellationToken; + + DefaultLauncherExecutionRequest(@Nullable LauncherDiscoveryRequest discoveryRequest, @Nullable TestPlan testPlan, + Collection executionListeners, CancellationToken cancellationToken) { + this.discoveryRequest = discoveryRequest; + this.testPlan = testPlan; + this.executionListeners = List.copyOf(executionListeners); + this.cancellationToken = cancellationToken; + } + + @Override + public Optional getDiscoveryRequest() { + return Optional.ofNullable(discoveryRequest); + } + + @Override + public Optional getTestPlan() { + return Optional.ofNullable(testPlan); + } + + @Override + 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/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/DiscoveryIssueCollector.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DiscoveryIssueCollector.java index 7714d3fb7618..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 @@ -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; @@ -18,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; @@ -51,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 @@ -78,7 +80,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 +98,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; } @@ -126,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(() -> 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)); - 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 7d2e7c529d4e..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 @@ -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; @@ -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) { @@ -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/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index 0536b5134736..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 @@ -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; @@ -23,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; @@ -56,13 +58,13 @@ public EngineExecutionOrchestrator() { } void execute(InternalTestPlan internalTestPlan, NamespacedHierarchicalStore requestLevelStore, - TestExecutionListener... 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)); } /** @@ -74,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. @@ -100,7 +107,7 @@ private void execute(InternalTestPlan internalTestPlan, EngineExecutionListener else { execute(discoveryResult, buildEngineExecutionListener(parentEngineExecutionListener, testExecutionListener, testPlan), - requestLevelStore); + requestLevelStore, cancellationToken); } testExecutionListener.testPlanExecutionFinished(testPlan); } @@ -161,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"); @@ -169,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); } } @@ -184,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() // @@ -200,21 +209,27 @@ 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); } } 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( - TestExecutionListener... listeners) { - if (listeners.length == 0) { + Collection listeners) { + if (listeners.isEmpty()) { return this.listenerRegistry; } return ListenerRegistry.copyOf(this.listenerRegistry).addAll(listeners); @@ -223,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/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/LauncherConfigurationParameters.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java index cfea8896710d..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; @@ -204,7 +207,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) { @@ -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/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..161800c6aeca --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherExecutionRequestBuilder.java @@ -0,0 +1,110 @@ +/* + * 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 java.util.Objects.requireNonNullElseGet; +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.engine.CancellationToken; +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 @Nullable CancellationToken cancellationToken; + + 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; + } + + /** + * 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(this.discoveryRequest, this.testPlan, this.executionListeners, + requireNonNullElseGet(this.cancellationToken, CancellationToken::disabled)); + } + +} 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/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java index eebd6de021f7..2e7355f90816 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java @@ -66,4 +66,7 @@ private static Logger getLogger() { return LoggerFactory.getLogger(ServiceLoaderRegistry.class); } + private ServiceLoaderRegistry() { + } + } 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-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/jfr/JfrUtils.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/JfrUtils.java index 7ec83dd91fbb..f501098a23de 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/JfrUtils.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/jfr/JfrUtils.java @@ -35,4 +35,7 @@ private static boolean isJfrAvailable() { return System.getProperty("org.graalvm.nativeimage.imagecode") == null // && ReflectionSupport.tryToLoadClass("jdk.jfr.FlightRecorder").toOptional().isPresent(); } + + private JfrUtils() { + } } 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/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/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/TagExpressions.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java index 350e6f4d7a68..03fd17e617dd 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java @@ -104,4 +104,7 @@ public String toString() { }; } + private TagExpressions() { + } + } 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-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-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java index c3909dd105df..6232c8b760c4 100644 --- a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java @@ -33,4 +33,7 @@ public static LauncherConfig.Builder createLauncherConfigBuilderWithDisabledServ .enableLauncherSessionListenerAutoRegistration(false); } + private LauncherFactoryForTestingPurposesOnly() { + } + } diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java index 7beef2b5f0f1..528d79724c99 100644 --- a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/NamespacedHierarchicalStoreProviders.java @@ -24,4 +24,7 @@ public static NamespacedHierarchicalStore 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/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-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/legacy/xml/XmlReportWriter.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java index 5847b73cbb7c..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 { @@ -294,7 +293,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-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-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-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-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-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-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..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,9 +11,9 @@ dependencies { api(projects.junitPlatformSuiteApi) compileOnlyApi(libs.apiguardian) - compileOnly(libs.jspecify) + compileOnlyApi(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..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,10 +18,9 @@ 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.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 98% 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..2c00b7f90c85 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; @@ -122,4 +122,7 @@ private static Stream uniqueStreamOf(T[] elements) { return Arrays.stream(elements).distinct(); } + private AdditionalDiscoverySelectors() { + } + } 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..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 @@ -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; @@ -58,11 +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(); - executionOrchestrator.execute(discoveryResult, parentEngineExecutionListener, listener, requestLevelStore); + executionOrchestrator.execute(discoveryResult, executionListener, listener, requestLevelStore, + cancellationToken); return listener.getSummary(); } 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 79% 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..72e486adb196 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); @@ -371,7 +298,7 @@ public SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Cl 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) // @@ -397,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; @@ -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)); } @@ -485,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, @@ -555,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-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..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 @@ -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; @@ -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; @@ -50,7 +51,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 +118,8 @@ void discover() { // @formatter:off LauncherDiscoveryRequest request = discoveryRequestBuilder - .filterStandardClassNamePatterns(true) - .enableImplicitConfigurationParameters(false) + .filterStandardClassNamePatterns() + .disableImplicitConfigurationParameters() .parentConfigurationParameters(configurationParameters) .applyConfigurationParametersFromSuite(suiteClass) .outputDirectoryProvider(outputDirectoryProvider) @@ -149,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) { @@ -177,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; } @@ -188,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/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-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-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..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; @@ -102,7 +104,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 +161,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 +219,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); } /** @@ -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-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/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/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/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..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 @@ -70,7 +70,8 @@ public void execute(ExecutionRequest request) { EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); 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/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(); 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..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 @@ -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); } @@ -248,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/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/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/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..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 @@ -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; @@ -42,12 +42,12 @@ 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.CancellationToken; 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; @@ -58,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; @@ -65,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; @@ -588,6 +590,7 @@ public static class DynamicSuiteRunner extends Runner { private final Class testClass; + @SuppressWarnings("RedundantModifier") public DynamicSuiteRunner(Class testClass) { this.testClass = testClass; } @@ -628,6 +631,7 @@ public static class DynamicAndStaticChildrenRunner extends Runner { private final Class testClass; + @SuppressWarnings("RedundantModifier") public DynamicAndStaticChildrenRunner(Class testClass) { this.testClass = testClass; } @@ -683,6 +687,7 @@ public static class MisbehavingChildlessRunner extends Runner { private final Class testClass; + @SuppressWarnings("RedundantModifier") public MisbehavingChildlessRunner(Class testClass) { this.testClass = testClass; } @@ -865,7 +870,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 +893,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"; @@ -927,6 +932,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)); } @@ -936,14 +967,22 @@ 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) { - 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()); + when(executionRequest.getCancellationToken()).thenReturn(CancellationToken.disabled()); + testEngine.execute(executionRequest); } private static LauncherDiscoveryRequest request(Class testClass) { 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(); + } +} 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/condition/ConditionEvaluationResultTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java new file mode 100644 index 000000000000..e82e5fa6df53 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/ConditionEvaluationResultTests.java @@ -0,0 +1,128 @@ +/* + * 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']"); + } + + @BlankReasonsTest + void enabledWithBlankReason(@Nullable String reason) { + var result = ConditionEvaluationResult.enabled(reason); + + assertThat(result.isDisabled()).isFalse(); + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = true, 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']"); + } + + @BlankReasonsTest + void disabledWithBlankDefaultReason(@Nullable String reason) { + var result = ConditionEvaluationResult.disabled(reason); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); + } + + @BlankReasonsTest + void disabledWithDefaultReasonAndBlankCustomReason(@Nullable String customReason) { + var result = ConditionEvaluationResult.disabled("default", customReason); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).contains("default"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default']"); + } + + @BlankReasonsTest + void disabledWithBlankDefaultReasonAndCustomReason(@Nullable String reason) { + var result = ConditionEvaluationResult.disabled(reason, "custom"); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).contains("custom"); + assertThat(result).asString().isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'custom']"); + } + + @BlankReasonsTest + void disabledWithBlankDefaultReasonAndBlankCustomReason(@Nullable String reason) { + // We intentionally use the reason as both the default and custom reason. + var result = ConditionEvaluationResult.disabled(reason, reason); + + assertThat(result.isDisabled()).isTrue(); + assertThat(result.getReason()).isEmpty(); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = '']"); + } + + @Test + void disabledWithDefaultReasonAndCustomReason() { + 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"); + assertThat(result).asString()// + .isEqualTo("ConditionEvaluationResult [enabled = false, reason = 'default ==> custom']"); + } + + @Retention(RetentionPolicy.RUNTIME) + @ParameterizedTest(name = "[{index}] reason=\"{0}\"") + @NullSource + @ValueSource(strings = { "", " ", " ", "\t", "\n" }) + @interface BlankReasonsTest { + } + +} 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/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/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/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/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/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/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/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/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/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 2599244c739c..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; @@ -409,7 +408,7 @@ private ExtensionContext createExtensionContextForFilePublishing(Path tempDir, } @Test - @SuppressWarnings("resource") + @SuppressWarnings({ "resource", "deprecation" }) void usingStore() { var methodTestDescriptor = methodDescriptor(); var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); @@ -436,14 +435,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)); } @@ -451,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)); @@ -552,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/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 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..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 @@ -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( @@ -257,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() { @@ -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/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); 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/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/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")), 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/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/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/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/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..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 @@ -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 { @@ -87,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/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 f8dc13677f1f..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()), @@ -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.trim() // + - 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()); @@ -830,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; } @@ -2091,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 @@ -2110,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(); } @@ -2123,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/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) { 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..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 @@ -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; @@ -255,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 @@ -292,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")))); } /** @@ -303,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 @@ -324,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 @@ -400,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); @@ -410,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 } @@ -438,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 @@ -488,17 +492,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)); } @@ -519,14 +526,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); } @@ -545,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 @@ -576,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")))); } @@ -597,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 = "))); } /** @@ -612,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 = []"))); } /** @@ -631,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 = []"))); } /** @@ -653,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 = {}"))); } /** @@ -675,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 @@ -784,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 = []"))// ); } @@ -1120,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) { @@ -1205,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 @@ -1214,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 @@ -1222,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 @@ -1255,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")))); } @@ -1266,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 @@ -1276,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 @@ -1286,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 @@ -1296,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 @@ -1307,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 @@ -1318,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")))); } @@ -1338,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) { @@ -1385,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 @@ -2229,7 +2238,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()); } @@ -2518,7 +2527,7 @@ static class ArgumentsAggregatorWithConstructorParameter extends SimpleArguments private final String value; - public ArgumentsAggregatorWithConstructorParameter(String value) { + ArgumentsAggregatorWithConstructorParameter(String value) { this.value = value; } @@ -2566,11 +2575,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..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,9 +167,8 @@ void size() { private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationIndex, @Nullable Object... arguments) { - var context = mock(ExtensionContext.class); var classLoader = DefaultArgumentsAccessorTests.class.getClassLoader(); - return DefaultArgumentsAccessor.create(context, invocationIndex, classLoader, arguments); + 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/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 ") // 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/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/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/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) } } diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 604850053ece..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 @@ -37,7 +36,6 @@ dependencies { testImplementation(projects.junitPlatformConsole) testImplementation(projects.junitPlatformEngine) testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteCommons) testImplementation(projects.junitPlatformSuiteEngine) // --- Things we are testing with --------------------------------------------- @@ -141,9 +139,6 @@ tasks { } eclipse { - classpath { - plusConfigurations.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - } classpath.file.whenMerged { this as Classpath entries.filterIsInstance().forEach { @@ -154,9 +149,3 @@ eclipse { } } } - -idea { - module { - scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) - } -} 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-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/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 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/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/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"); } 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))); 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/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index 85fb2c028746..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 @@ -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,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.ConfigurationParameters; +import org.junit.platform.engine.CancellationToken; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -53,6 +52,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; @@ -70,6 +70,8 @@ class HierarchicalTestExecutorTests { @Mock EngineExecutionListener listener; + CancellationToken cancellationToken = CancellationToken.create(); + MyEngineExecutionContext rootContext = new MyEngineExecutionContext(); HierarchicalTestExecutor executor; @@ -80,8 +82,10 @@ 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); + when(request.getCancellationToken()).thenReturn(cancellationToken); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } @@ -552,6 +556,7 @@ void executesDynamicTestDescriptorsWithCustomListener() { } @Test + @MockitoSettings(strictness = LENIENT) void canAbortExecutionOfDynamicChild() throws Exception { var leafUniqueId = UniqueId.root("leaf", "child leaf"); @@ -706,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 { 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-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() // 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..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 @@ -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; @@ -181,7 +186,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 +224,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 +248,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 +272,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 +297,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 +324,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 +347,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 +367,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 +435,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 +446,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 +461,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 +479,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 +561,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 +598,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 +649,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()); @@ -891,8 +905,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId } @Test - void fallsBackToErrorSeverityIfCriticalSeverityIsConfiguredIncorrectly( - @TrackLogRecords LogRecordListener listener) { + void failsIfCriticalSeverityIsConfiguredIncorrectly() { var engine = new TestEngineStub("engine-id") { @Override @@ -911,18 +924,12 @@ public void execute(ExecutionRequest request) { } }; - var result = execute(engine, request -> request // - .configurationParameter(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, "wrong")); + var exception = assertThrows(JUnitException.class, () -> execute(engine, request -> request // + .configurationParameter(LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME, "wrong"))); - assertThat(result.testExecutionResult().getStatus()).isEqualTo(Status.SUCCESSFUL); - - 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); } @@ -975,11 +982,70 @@ 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") { + @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<>(); @@ -994,15 +1060,19 @@ private static ReportedData execute(TestEngine engine, UnaryOperator 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)), 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 9d8899b9402a..1ade0e0721de 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 @@ -169,11 +169,14 @@ void inheritedPropertyOverridesSystemProperty() { @Test void getValueInExtensionContext() { + var summary = new SummaryGeneratingListener(); var request = LauncherDiscoveryRequestBuilder.request() // .configurationParameter("thing", "one else!") // - .selectors(DiscoverySelectors.selectClass(Something.class)).build(); - var summary = new SummaryGeneratingListener(); - LauncherFactory.create().execute(request, summary); + .selectors(DiscoverySelectors.selectClass(Something.class)) // + .forExecution() // + .listeners(summary) // + .build(); + LauncherFactory.create().execute(request); assertEquals(0, summary.getSummary().getTestsFailedCount()); } 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 9ce3bb561f58..18093ec55367 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 @@ -80,7 +80,7 @@ void testExecutionListenerIsLoadedViaServiceApi() { NoopTestExecutionListener.called = false; - launcher.execute(request().build()); + launcher.execute(request().forExecution().build()); assertTrue(NoopTestExecutionListener.called); }); @@ -101,7 +101,7 @@ void testExecutionListenersExcludedViaConfigParametersIsNotLoadedViaServiceApi( UnusedTestExecutionListener.called = false; AnotherUnusedTestExecutionListener.called = false; - launcher.execute(request().build()); + launcher.execute(request().forExecution().build()); var logMessage = listener.stream(ServiceLoaderRegistry.class) // .map(LogRecord::getMessage) // @@ -316,19 +316,25 @@ public void execute(ExecutionRequest request) { .enableTestEngineAutoRegistration(false) // .addTestEngines(engine) // .build(); - var launcher = LauncherFactory.create(config); - var request = request().configurationParameter(LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, - "false").build(); AtomicReference 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..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 @@ -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()); } @Test + @SuppressWarnings({ "deprecation", "resource" }) 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(); @@ -93,8 +113,8 @@ void callsRegisteredListenersWhenLauncherIsUsedViaSession() { } @Test + @SuppressWarnings({ "deprecation", "resource" }) 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/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/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/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/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/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..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 @@ -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); @@ -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); } } 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 91b8b5d73676..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,15 +23,19 @@ 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; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; 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; @@ -38,20 +43,21 @@ 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.ConfigurationParameters; +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; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.engine.support.store.Namespace; 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; @@ -629,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); @@ -644,13 +645,88 @@ void suiteEnginePassesRequestLevelStoreToSuiteTestDescriptors() { EngineExecutionListener listener = mock(EngineExecutionListener.class); NamespacedHierarchicalStore requestLevelStore = NamespacedHierarchicalStoreProviders.dummyNamespacedHierarchicalStore(); + var cancellationToken = CancellationToken.create(); - 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); + 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 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 93% 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..b1b513c6900a 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 { @@ -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-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)) // 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..2f13dba504a2 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -1,7 +1,9 @@ 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 import java.time.Duration @@ -56,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) @@ -150,6 +154,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")) } @@ -174,7 +184,6 @@ val test by testing.suites.getting(JvmTestSuite::class) { implementation(testFixtures(projects.junitPlatformReporting)) implementation(libs.snapshotTests.junit5) implementation(libs.snapshotTests.xml) - } targets { @@ -227,7 +236,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 +263,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 +271,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 +290,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() } } 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 6a59f78e05fd..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,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" + // 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 { mavenCentral() 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..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 @@ -9,7 +9,8 @@ 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 exports org.junit.jupiter.api.util to org.junit.jupiter.engine 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-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-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-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..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,11 +1,10 @@ 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 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/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 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..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 @@ -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( // @@ -157,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/main/java/platform/tooling/support/Helper.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java index 0df95dd15ca8..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); @@ -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() { + } } 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/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/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"); } } 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/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"); } } 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 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/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index 7882fc19847b..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,17 +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.copyAll(lib); loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -136,6 +126,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") // @@ -159,30 +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/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); } 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")); } 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) // 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 - + - + - + - + diff --git a/settings.gradle.kts b/settings.gradle.kts index 9c65c66f87c3..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() @@ -85,7 +87,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")