diff --git a/.github/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml index 5952a1e8c3aa..a443402a9720 100644 --- a/.github/workflows/close-inactive-issues.yml +++ b/.github/workflows/close-inactive-issues.yml @@ -11,7 +11,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 with: only-labels: "status: waiting-for-feedback" days-before-stale: 14 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e5050471788c..e7d55f0dd380 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3 + uses: github/codeql-action/init@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3 + uses: github/codeql-action/analyze@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84861342054a..97645a94623b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: with: fetch-depth: 1 - name: Install GraalVM - uses: graalvm/setup-graalvm@c09e29bb115a83bd4b7c7e99bb46e2e8a1c50466 # v1 + uses: graalvm/setup-graalvm@aafbedb8d382ed0ca6167d3a051415f20c859274 # v1 with: distribution: graalvm-community version: 'latest' @@ -39,7 +39,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@1e68e06f1dbfde0e4cefc87efeba9e4643565303 # v5 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -91,7 +91,7 @@ jobs: publish -x check \ prepareGitHubAttestation - name: Generate build provenance attestations - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: documentation/build/attestation/*.jar diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 83eb112f1b21..0900c1a0d83f 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@b6a472f63d85b9c78a3ac5e89422239fc15e9b3c # v3 + uses: github/codeql-action/upload-sarif@f6091c0113d1dcf9b98e269ee48e8a7e51b7bdd4 # v3 with: sarif_file: results.sarif diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 873723ee9d34..c1dad08c57a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,7 @@ jobs: :verifyArtifactsInStagingRepositoryAreReproducible \ --remote-repo-url=${{ env.STAGING_REPO_URL }} - name: Generate build provenance attestations - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: build/repo/**/*.jar - name: Upload local repository for later jobs diff --git a/.github/workflows/sanitize-closed-issues.yml b/.github/workflows/sanitize-closed-issues.yml new file mode 100644 index 000000000000..6f0721a0d2db --- /dev/null +++ b/.github/workflows/sanitize-closed-issues.yml @@ -0,0 +1,70 @@ +name: Sanitizes assigned labels and milestone on closed issues +on: + issues: + types: + - closed +permissions: read-all +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const issue = await github.rest.issues.get({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + const originalLabels = issue.data.labels.map(l => l.name); + const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new"); + if (newLabels.length !== originalLabels.length) { + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: newLabels, + }); + } + if (issue.data.state_reason === "not_planned") { + if (issue.data.milestone) { + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + milestone: null, + }); + } + const statusLabels = newLabels.filter(l => l.startsWith("status: ")); + if (statusLabels.length === 0) { + await github.rest.issues.createComment({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Please assign a status label to this issue.", + }); + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + }); + } + } else { + if (!(newLabels.includes("type: task") || newLabels.includes("type: question")) && !issue.data.milestone) { + await github.rest.issues.createComment({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Please assign a milestone to this issue or label it with `type: task` or `type: question`.", + }); + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + }); + } + } diff --git a/.github/workflows/unlabel-closed-issues.yml b/.github/workflows/unlabel-closed-issues.yml deleted file mode 100644 index 00a4aa525f9c..000000000000 --- a/.github/workflows/unlabel-closed-issues.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Check/remove status labels from closed issues -on: - issues: - types: - - closed -permissions: read-all -jobs: - label_issues: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - with: - script: | - const issue = await github.rest.issues.get({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }); - const originalLabels = issue.data.labels.map(l => l.name); - const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new"); - if (newLabels.length !== originalLabels.length) { - await github.rest.issues.update({ - issue_number: issue.data.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: newLabels, - }); - } - const statusLabels = newLabels.filter(l => l.startsWith("status: ")); - if (issue.data.state_reason === "not_planned" && statusLabels.length === 0) { - await github.rest.issues.createComment({ - issue_number: issue.data.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: "Please assign a status label to this issue.", - }); - await github.rest.issues.update({ - issue_number: issue.data.number, - owner: context.repo.owner, - repo: context.repo.repo, - state: "open", - }); - } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 081a337257e9..135a6cd3456c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,14 @@ # Contributing +## Getting Started + +We welcome new contributors to the project! +If you're interested, please check for [issues labeled with `up-for-grabs` +that are not yet in progress](https://github.com/junit-team/junit5/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Aup-for-grabs%20-label%3A%22status%3A%20in%20progress%22). +Generally, before you work on an issue, post a comment and ask whether it can be started. +Please wait for the core team to respond and assign the issue to you before making any code +changes. + ## JUnit Contributor License Agreement - You will only Submit Contributions where You have authored 100% of the content. diff --git a/RELEASING.md b/RELEASING.md index e22831147713..641960cd0f8f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -21,7 +21,6 @@ - [ ] Post about the new release: - [ ] [Mastodon](https://fosstodon.org/@junit) - [ ] [Bluesky](https://bsky.app/profile/junit.org) - - [ ] [Twitter/X](https://x.com/junitteam) ### Preview releases (milestones and release candidates) diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc index 5c15b26ba6fa..0abbde8c9af2 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -37,22 +37,26 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-platform-new-features-and-improvements]] ==== New Features and Improvements -* New `addResourceContainerSelectorResolver()` in `EngineDiscoveryRequestResolver.Builder` to - support the discovery of class path resource based tests, analogous to the - `addClassContainerSelectorResolver()`. -* Introduce `ReflectionSupport.makeAccessible(Field)` for third-party use rather than - calling the internal `ReflectionUtils.makeAccessible(Field)` method directly. -* Support both the primitive type `void` and the wrapper type `Void` in the internal - `ReflectionUtils` to allow `String` to `Class` conversion in parameterized tests. -* New `--exclude-methodname` and `--include-methodname` options added to the - `ConsoleLauncher` to include or exclude methods based on fully qualified method names - without parameters. For example, `--exclude-methodname=^org\.example\..+#methodname` - will exclude all methods called `methodName` under package `org.example`. -* Add support for passing line and column number to `ConsoleLauncher` via - `--select-file` and `--select-resource`. * `ConsoleLauncher` now accepts multiple values for all `--select` options. -* Add `--select-unique-id` support to ConsoleLauncher. -* Add `getOutputDirectoryProvider()` method to `EngineDiscoveryRequest` and `TestPlan` to +* `ConsoleLauncher` now supports a `--select-unique-id` option to select containers and + tests by unique ID. +* `ConsoleLauncher` supports new `--exclude-methodname` and `--include-methodname` options + to include or exclude methods based on fully qualified method names without parameters. + For example, `--exclude-methodname=^org\.example\..+#methodname` will exclude all + methods called `methodName` under package `org.example`. +* The `--select-file` and `--select-resource` options for the `ConsoleLauncher` now + support line and column numbers. +* New `ReflectionSupport.makeAccessible(Field)` public utility method to be used by third + parties instead of calling the internal `ReflectionUtils.makeAccessible(Field)` method + directly. +* The `ReflectionSupport.tryToLoadClass(...)` utility methods now support lookups for the + `"void"` pseudo-type, which indirectly supports `String` to `Class` conversion for + `"void"` in parameterized tests in JUnit Jupiter. +* New `addResourceContainerSelectorResolver()` method in + `EngineDiscoveryRequestResolver.Builder` which supports the discovery of class path + resource based tests, analogous to the existing `addClassContainerSelectorResolver()` + method. +* New `getOutputDirectoryProvider()` method in `EngineDiscoveryRequest` and `TestPlan` to allow test engines to publish/attach files to containers and tests by calling `EngineExecutionListener.fileEntryPublished(...)`. Registered `TestExecutionListeners` can then access these files by overriding the `fileEntryPublished(...)` method. @@ -69,7 +73,6 @@ JUnit repository on GitHub. in the XML report. * Output written to `System.out` and `System.err` from non-test threads is now attributed to the most recent test or container that was started or has written output. -* Introduced contracts for Kotlin-specific assertion methods. * New public interface `ClasspathScanner` allowing third parties to provide a custom implementation for scanning the classpath for classes and resources. @@ -80,65 +83,69 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-jupiter-bug-fixes]] ==== Bug Fixes -* ❓ +* Provide _runtime_ enclosing types of `@Nested` test classes and contained test methods + to `DisplayNameGenerator` implementations. Prior to this change, such generators were + only able to access the enclosing class in which `@Nested` was declared, but they could + not access the concrete runtime type of the enclosing instance. [[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]] ==== Deprecations and Breaking Changes -* When injecting `TestInfo` into test class constructors it now contains data of the test - method the test class instance is being created for unless the test instance lifecycle - is set to `PER_CLASS` (in which case it continues to contain the data of the test - class). If you require the `TestInfo` of the test class, you can implement a class-level - lifecycle method (e.g., `@BeforeAll`) and inject `TestInfo` into that method. +* When injecting `TestInfo` into test class constructors, the `TestInfo` now contains data + for the test method for which the test class instance is being created, unless the test + instance lifecycle is set to `PER_CLASS` (in which case it continues to contain the data + for the test class). If you require the `TestInfo` of the test class, you can implement + a `@BeforeAll` lifecycle method and inject `TestInfo` into that method. * When injecting `TestReporter` into test class constructors the published report entries - are now associated with the test method rather than the test class unless the test - instance lifecycle is set to `PER_CLASS` (in which case they will continue to be - associated with the test class). If you want to publish report entries for the test - class, you can implement a class-level lifecycle method (e.g., `@BeforeAll`) and inject + are now associated with the test method rather than the test class, unless the test + instance lifecycle is set to `PER_CLASS` (in which case the published report entries + will continue to be associated with the test class). If you want to publish report + entries for the test class, you can implement a `@BeforeAll` lifecycle method and inject `TestReporter` into that method. [[release-notes-5.12.0-M1-junit-jupiter-new-features-and-improvements]] ==== New Features and Improvements +* Kotlin contracts for Kotlin-specific assertion methods in `Assertions`. +* `@TempDir` is now supported on test class constructors. +* Shared resource locks may now be determined programmatically at runtime via the new + `@ResourceLock#providers` attribute that accepts implementations of + `ResourceLocksProvider`. +* Shared resource locks for _direct_ child nodes may now be configured via the new + `@ResourceLock(target = CHILDREN)` attribute. This may improve parallelization when + a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. +* `@EnumSource` has new `from` and `to` attributes that support the selection of enum + constants within the specified range. * In a `@ParameterizedTest` method, a `null` value can now be supplied for Java Date/Time types such as `LocalDate` if the new `nullable` attribute in `@JavaTimeConversionPattern` is set to `true`. +* The new `@ParameterizedTest(allowZeroInvocations = true)` attribute allows to specify that + the absence of invocations is expected in some cases and should not cause a test failure. +* Parameterized tests now support argument count validation. If the + `junit.jupiter.params.argumentCountValidation=strict` configuration parameter or the + `@ParameterizedTest(argumentCountValidation = STRICT)` attribute is set, any mismatch + between the declared number of arguments and the number of arguments provided by the + arguments source will result in an error. By default, it is still only an error if there + are fewer arguments provided than declared. * `ArgumentsProvider` (declared via `@ArgumentsSource`), `ArgumentConverter` (declared via `@ConvertWith`), and `ArgumentsAggregator` (declared via `@AggregateWith`) implementations can now use constructor injection from registered `ParameterResolver` extensions. -* Extensions based on `TestTemplateInvocationContextProvider` can now allow returning zero - invocation contexts by overriding the new `mayReturnZeroTestTemplateInvocationContexts` - method. -* The new `@ParameterizedTest(allowZeroInvocations = true)` attribute allows to specify that - the absence of invocations is expected in some cases and should not cause a test failure. -* Allow determining "shared resources" at runtime via the new `@ResourceLock#providers` - attribute that accepts implementations of `ResourceLocksProvider`. -* Allow declaring "shared resources" for _direct_ child nodes via the new - `@ResourceLock(target = CHILDREN)` attribute. This may improve parallelization when - a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. +* `TestTemplateInvocationContextProvider` extensions can now signal that they may + potentially return zero invocation contexts by overriding the new + `mayReturnZeroTestTemplateInvocationContexts()` method. * Extensions that implement `TestInstancePreConstructCallback`, `TestInstanceFactory`, `TestInstancePostProcessor`, `ParameterResolver`, or `InvocationInterceptor` may override the `getTestInstantiationExtensionContextScope()` method to enable receiving a test-scoped `ExtensionContext` in `Extension` methods called during test class instantiation. This behavior will become the default in future versions of JUnit. -* `@TempDir` is now supported on test class constructors. -* Parameterized tests now support argument count validation. - If the `junit.jupiter.params.argumentCountValidation=strict` configuration parameter - or the `@ParameterizedTest(argumentCountValidation = STRICT)` attribute is set, any - mismatch between the declared number of arguments and the number of arguments provided - by the arguments source will result in an error. By default, it's still only an error if - there are fewer arguments provided than declared. -* The new `PreInterruptCallback` extension point defines the API for `Extensions` that - wish to be called prior to invocations of `Thread#interrupt()` by the `@Timeout` - extension. +* The new `PreInterruptCallback` interface defines the API for `Extensions` that wish to + be called prior to invocations of `Thread#interrupt()` by the `@Timeout` extension. * When enabled via the `junit.jupiter.execution.timeout.threaddump.enabled` configuration parameter, an implementation of `PreInterruptCallback` is registered that writes a thread dump to `System.out` prior to interrupting a test thread due to a timeout. * `TestReporter` now allows publishing files for a test method or test class which can be used to include them in test reports, such as the Open Test Reporting format. -* New `from` and `to` attributes added to `@EnumSource` to support range selection of - enum constants. * Auto-registered extensions can now be <<../user-guide/index.adoc#extensions-registration-automatic-filtering, filtered>> using include and exclude patterns that can be specified as configuration parameters. @@ -160,6 +167,6 @@ JUnit repository on GitHub. [[release-notes-5.12.0-M1-junit-vintage-new-features-and-improvements]] ==== New Features and Improvements -* Introduced support for executing top-level test classes in parallel. Please refer to the +* Support for executing top-level test classes in parallel. Please refer to the <<../user-guide/index.adoc#migrating-from-junit4-parallel-execution, User Guide>> for more information. diff --git a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java index 31f4c37e6bb3..4abe7297e357 100644 --- a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java +++ b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java @@ -19,6 +19,7 @@ import java.lang.reflect.Method; import java.util.Collections; +import java.util.List; import java.util.Properties; import java.util.Set; @@ -68,7 +69,8 @@ void canSetCustomPropertyToBanana() { static class Provider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { ResourceAccessMode mode = testMethod.getName().startsWith("canSet") ? READ_WRITE : READ; return Collections.singleton(new Lock(SYSTEM_PROPERTIES, mode)); } diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties deleted file mode 100644 index 63e5bbdf4845..000000000000 --- a/gradle/gradle-daemon-jvm.properties +++ /dev/null @@ -1,2 +0,0 @@ -#This file is generated by updateDaemonJvm -toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4f3aea0ac55d..9069b20ba753 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ asciidoctorj-pdf = "2.3.19" asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.27.3" bnd = "7.1.0" -checkstyle = "10.21.1" +checkstyle = "10.21.2" eclipse = "4.34.0" jackson = "2.18.2" jacoco = "0.8.12" @@ -35,7 +35,7 @@ bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } commons-io = { module = "commons-io:commons-io", version = "2.18.0" } -groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.24" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.25" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } @@ -90,13 +90,13 @@ asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciid asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-plugins" } 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.0.2" } -develocity = { id = "com.gradle.develocity", version = "3.19" } +commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.1" } +develocity = { id = "com.gradle.develocity", version = "3.19.1" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.9.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.0" } jmh = { id = "me.champeau.jmh", version = "0.7.2" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } -plantuml = { id = "io.freefair.plantuml", version = "8.11" } +plantuml = { id = "io.freefair.plantuml", version = "8.12" } shadow = { id = "com.gradleup.shadow", version = "8.3.5" } spotless = { id = "com.diffplug.spotless", version = "6.25.0" } -versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } +versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 41935db0aa36..163866db2805 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,3 +1,9 @@ +val expectedJavaVersion = JavaVersion.VERSION_21 +val actualJavaVersion = JavaVersion.current() +require(actualJavaVersion == expectedJavaVersion) { + "The JUnit 5 build must be executed with Java ${expectedJavaVersion.majorVersion}. Currently executing with Java ${actualJavaVersion.majorVersion}." +} + dependencyResolutionManagement { versionCatalogs { create("libs") { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1b837a19c22..d71047787f80 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=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME 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 810d180e3da8..89f7784d8c57 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 @@ -10,11 +10,15 @@ package org.junit.jupiter.api; +import static java.util.Collections.emptyList; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ModifierSupport.isStatic; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -74,7 +78,8 @@ public interface DisplayNameGenerator { /** * Generate a display name for the given top-level or {@code static} nested test class. * - *

If it returns {@code null}, the default display name generator will be used instead. + *

If this method returns {@code null}, the default display name + * generator will be used instead. * * @param testClass the class to generate a name for; never {@code null} * @return the display name for the class; never blank @@ -82,19 +87,52 @@ public interface DisplayNameGenerator { String generateDisplayNameForClass(Class testClass); /** - * Generate a display name for the given {@link Nested @Nested} inner test class. + * Generate a display name for the given {@link Nested @Nested} inner test + * class. * - *

If it returns {@code null}, the default display name generator will be used instead. + *

If this method returns {@code null}, the default display name + * generator will be used instead. * * @param nestedClass the class to generate a name for; never {@code null} * @return the display name for the nested class; never blank + * @deprecated in favor of {@link #generateDisplayNameForNestedClass(List, Class)} */ - String generateDisplayNameForNestedClass(Class nestedClass); + @API(status = DEPRECATED, since = "5.12") + @Deprecated + default String generateDisplayNameForNestedClass(Class nestedClass) { + throw new UnsupportedOperationException( + "Implement generateDisplayNameForNestedClass(List>, Class) instead"); + } + + /** + * Generate a display name for the given {@link Nested @Nested} inner test + * class. + * + *

If this method returns {@code null}, the default display name + * generator will be used instead. + * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code nestedClass}; never {@code null} + * @param nestedClass the class to generate a name for; never {@code null} + * @return the display name for the nested class; never blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return generateDisplayNameForNestedClass(nestedClass); + } /** * Generate a display name for the given method. * - *

If it returns {@code null}, the default display name generator will be used instead. + *

If this method returns {@code null}, the default display name + * generator will be used instead. * * @implNote The class instance supplied as {@code testClass} may differ from * the class returned by {@code testMethod.getDeclaringClass()} — for @@ -103,8 +141,42 @@ public interface DisplayNameGenerator { * @param testClass the class the test method is invoked on; never {@code null} * @param testMethod method to generate a display name for; never {@code null} * @return the display name for the test; never blank + * @deprecated in favor of {@link #generateDisplayNameForMethod(List, Class, Method)} */ - String generateDisplayNameForMethod(Class testClass, Method testMethod); + @API(status = DEPRECATED, since = "5.12") + @Deprecated + default String generateDisplayNameForMethod(Class testClass, Method testMethod) { + throw new UnsupportedOperationException( + "Implement generateDisplayNameForMethod(List>, Class, Method) instead"); + } + + /** + * Generate a display name for the given method. + * + *

If this method returns {@code null}, the default display name + * generator will be used instead. + * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. Similarly, the class instance + * supplied as {@code testClass} may differ from the class returned by + * {@code testMethod.getDeclaringClass()} — for example, when a test + * method is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} + * @param testClass the class the test method is invoked on; never {@code null} + * @param testMethod method to generate a display name for; never {@code null} + * @return the display name for the test; never blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return generateDisplayNameForMethod(testClass, testMethod); + } /** * Generate a string representation of the formal parameters of the supplied @@ -142,12 +214,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return nestedClass.getSimpleName(); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return testMethod.getName() + parameterTypesAsString(testMethod); } } @@ -168,7 +241,8 @@ public Simple() { } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { String displayName = testMethod.getName(); if (hasParameters(testMethod)) { displayName += ' ' + parameterTypesAsString(testMethod); @@ -202,13 +276,15 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass)); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass)); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod)); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return replaceUnderscores( + super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod)); } private static String replaceUnderscores(String name) { @@ -243,18 +319,21 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return getSentenceBeginning(nestedClass); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return getSentenceBeginning(enclosingInstanceTypes, nestedClass); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) - + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return getSentenceBeginning(enclosingInstanceTypes, testClass) + getFragmentSeparator(testClass) + + getGeneratorFor(testClass).generateDisplayNameForMethod(enclosingInstanceTypes, testClass, + testMethod); } - private String getSentenceBeginning(Class testClass) { - Class enclosingClass = testClass.getEnclosingClass(); + private String getSentenceBeginning(List> enclosingInstanceTypes, Class testClass) { + Class enclosingClass = enclosingInstanceTypes.isEmpty() ? null + : enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1); boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); Optional displayName = findAnnotation(testClass, DisplayName.class)// .map(DisplayName::value).map(String::trim); @@ -280,10 +359,16 @@ private String getSentenceBeginning(Class testClass) { .filter(IndicativeSentences.class::equals)// .isPresent(); - String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); + List> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList() + : enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1); + + String prefix = (buildPrefix + ? getSentenceBeginning(remainingEnclosingInstanceTypes, enclosingClass) + + getFragmentSeparator(testClass) + : ""); - return prefix + displayName.orElseGet( - () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); + return prefix + displayName.orElseGet(() -> getGeneratorFor(testClass).generateDisplayNameForNestedClass( + remainingEnclosingInstanceTypes, testClass)); } /** 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 59fece5cd109..3d5ec178f015 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 @@ -418,6 +418,28 @@ default void publishReportEntry(String value) { */ Store getStore(Namespace namespace); + /** + * Returns the store for session-level data. + * + *

This store is used to store data that is scoped to the session level. + * The data stored in this store will be available throughout the entire session. + * + * @return the store for session-level data + * @since 5.13 + */ + Store getSessionLevelStore(); + + /** + * Returns the store for request-level data. + * + *

This store is used to store data that is scoped to the request level. + * The data stored in this store will be available only for the duration of the current request. + * + * @return the store for request-level data + * @since 5.13 + */ + Store getRequestLevelStore(); + /** * Get the {@link ExecutionMode} associated with the current test or container. * 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 a6a79b881513..31076236ecae 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 @@ -93,19 +93,19 @@ public interface TestTemplateInvocationContextProvider extends Extension { /** * Signal that this provider may provide zero - * {@linkplain TestTemplateInvocationContext invocation contexts} in the - * supplied {@code context}. + * {@linkplain TestTemplateInvocationContext invocation contexts} for the test + * template method represented by the supplied {@code context}. * *

If this method returns {@code false} (which is the default) and the * provider returns an empty stream from * {@link #provideTestTemplateInvocationContexts}, this will be considered - * an execution error. Override this method to ignore the absence of - * invocation contexts for this provider. + * an execution error. Override this method to return {@code true} to ignore + * the absence of invocation contexts for this provider. * * @param context the extension context for the test template method about * to be invoked; never {@code null} * @return {@code true} to allow zero contexts, {@code false} to fail - * execution in case of zero contexts. + * execution in case of zero contexts * * @since 5.12 */ diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java index e2d3af554630..24c85cb268c8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -21,16 +21,14 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; /** * {@code @ResourceLock} is used to declare that the annotated test class or test * method requires access to a shared resource identified by a key. * - *

The resource key is specified via {@link #value}. In addition, - * {@link #mode} allows you to specify whether the annotated test class or test - * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} or only + *

The resource key is specified via {@link #value}. In addition, {@link #mode} + * allows one to specify whether the annotated test class or test method requires + * {@link ResourceAccessMode#READ_WRITE READ_WRITE} or * {@link ResourceAccessMode#READ READ} access to the resource. In the former case, * execution of the annotated element will occur while no other test class or * test method that uses the shared resource is being executed. In the latter case, @@ -39,46 +37,38 @@ * other test that requires {@code READ_WRITE} access. * *

This guarantee extends to lifecycle methods of a test class or method. For - * example, if a test method is annotated with a {@code @ResourceLock} - * annotation the "lock" will be acquired before any - * {@link BeforeEach @BeforeEach} methods are executed and released after all - * {@link AfterEach @AfterEach} methods have been executed. + * example, if a test method is annotated with {@code @ResourceLock} the lock + * will be acquired before any {@link org.junit.jupiter.api.BeforeEach @BeforeEach} + * methods are executed and released after all + * {@link org.junit.jupiter.api.AfterEach @AfterEach} methods have been executed. * *

This annotation can be repeated to declare the use of multiple shared resources. * - *

Uniqueness of a shared resource is identified by both {@link #value()} and - * {@link #mode()}. Duplicated shared resources do not cause errors. + *

Uniqueness of a shared resource is determined by both the {@link #value()} + * and the {@link #mode()}. Duplicated shared resources do not cause errors. * *

Since JUnit Jupiter 5.4, this annotation is {@linkplain Inherited inherited} * within class hierarchies. * *

Since JUnit Jupiter 5.12, this annotation supports adding shared resources - * dynamically at runtime via {@link #providers}. - * - *

Resources declared "statically" using {@link #value()} and {@link #mode()} - * are combined with "dynamic" resources added via {@link #providers()}. - * For example, declaring resource "A" via {@code @ResourceLock("A")} - * and resource "B" via a provider returning {@code new Lock("B")} will result - * in two shared resources "A" and "B". + * dynamically at runtime via {@link #providers}. Resources declared "statically" + * using {@link #value()} and {@link #mode()} are combined with "dynamic" resources + * added via {@link #providers()}. For example, declaring resource "A" via + * {@code @ResourceLock("A")} and resource "B" via a provider returning + * {@code new Lock("B")} will result in two shared resources "A" and "B". * *

Since JUnit Jupiter 5.12, this annotation supports declaring "static" * shared resources for direct child nodes via the {@link #target()} - * attribute. - * - *

Using the {@link ResourceLockTarget#CHILDREN} in a class-level - * annotation has the same semantics as adding an annotation with the same - * {@link #value()} and {@link #mode()} to each test method and nested test - * class declared in this class. - * - *

This may improve parallelization when a test class declares a + * attribute. Using {@link ResourceLockTarget#CHILDREN} in a class-level annotation + * has the same semantics as adding an annotation with the same {@link #value()} + * and {@link #mode()} to each test method and nested test class declared in the + * annotated class. This may improve parallelization when a test class declares a * {@link ResourceAccessMode#READ READ} lock, but only a few methods hold - * {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. - * - *

Note that the {@code target = CHILDREN} means that - * {@link #value()} and {@link #mode()} no longer apply to a node - * declaring the annotation. However, the {@link #providers()} attribute - * remains applicable, and the target of "dynamic" shared resources - * added via implementations of {@link ResourceLocksProvider} is not changed. + * {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. Note that + * {@code target = CHILDREN} means that {@link #value()} and {@link #mode()} no + * longer apply to a node declaring the annotation. However, the {@link #providers()} + * attribute remains applicable, and the target of "dynamic" shared resources added + * via implementations of {@link ResourceLocksProvider} is not changed. * * @see Isolated * @see Resources @@ -116,28 +106,28 @@ ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; /** - * The target of a resource created from {@link #value()} and {@link #mode()}. - * - *

Defaults to {@link ResourceLockTarget#SELF SELF}. + * An array of one or more classes implementing {@link ResourceLocksProvider}. * - *

Note that using {@link ResourceLockTarget#CHILDREN} in - * a method-level annotation results in an exception. + *

Defaults to an empty array. * - * @see ResourceLockTarget + * @see ResourceLocksProvider.Lock * @since 5.12 */ @API(status = EXPERIMENTAL, since = "5.12") - ResourceLockTarget target() default ResourceLockTarget.SELF; + Class[] providers() default {}; /** - * An array of one or more classes implementing {@link ResourceLocksProvider}. + * The target of a resource created from {@link #value()} and {@link #mode()}. * - *

Defaults to an empty array. + *

Defaults to {@link ResourceLockTarget#SELF SELF}. * - * @see ResourceLocksProvider.Lock + *

Note that using {@link ResourceLockTarget#CHILDREN} in a method-level + * annotation results in an exception. + * + * @see ResourceLockTarget * @since 5.12 */ @API(status = EXPERIMENTAL, since = "5.12") - Class[] providers() default {}; + ResourceLockTarget target() default ResourceLockTarget.SELF; } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java index 883869134d48..94a7a0bf7d68 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java @@ -31,17 +31,17 @@ public enum ResourceLockTarget { /** * Add a shared resource to the direct children of the current node. * - *

Examples of "parent - child" relationship in the context of - * {@link ResourceLockTarget}: + *

Examples of "parent - child" relationships in the context of + * {@code ResourceLockTarget}: *

    - *
  • a test class - * - test methods and nested test classes declared in the class.
  • - *
  • a nested test class - * - test methods and nested test classes declared in the nested class. - *
  • - *
  • a test method - * - considered to have no children. Using {@code CHILDREN} for - * a test method results in an exception.
  • + *
  • test class: test methods and nested test classes + * declared in the test class are children of the test class.
  • + *
  • nested test class: test methods and nested test + * classes declared in the nested class are children of the nested test class. + *
  • + *
  • test method: test methods are not considered to have + * children. Using {@code CHILDREN} for a test method results in an + * exception.
  • *
*/ CHILDREN diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java index 6e531c478d4f..2cffc5d6101f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -14,24 +14,23 @@ import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.reflect.Method; +import java.util.List; import java.util.Objects; import java.util.Set; import org.apiguardian.api.API; -import org.junit.jupiter.api.Nested; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ToStringBuilder; /** - * {@code @ResourceLocksProvider} is used to add shared resources - * to a test class and / or its test methods dynamically at runtime. + * A {@code ResourceLocksProvider} is used to programmatically add shared resources + * to a test class or its test methods dynamically at runtime. * *

Each shared resource is represented by an instance of {@link Lock}. * - *

Adding shared resources using this interface has the same semantics - * as declaring them via {@code @ResourceLock(value, mode)} annotation - * but for some cases may be a more flexible and less verbose alternative - * since it allows to add resources programmatically. + *

Adding shared resources via this API has the same semantics as declaring + * them declaratively via {@link ResourceLock @ResourceLock(value, mode)}, but for + * some use cases the programmatic approach may be more flexible and less verbose. * *

Implementations must provide a no-args constructor. * @@ -45,16 +44,16 @@ public interface ResourceLocksProvider { /** - * Add shared resources to a test class. + * Add shared resources for a test class. * - *

Invoked in case a test class or its parent class - * is annotated with {@code @ResourceLock(providers)}. + *

Invoked in case a test class or its parent class is annotated with + * {@code @ResourceLock(providers)}. * - * @apiNote Adding {@linkplain Lock a shared resource} with this method - * has the same semantics as annotating a test class - * with analogous {@code @ResourceLock(value, mode)}. + * @apiNote Adding {@linkplain Lock a shared resource} via this method has + * the same semantics as annotating a test class with an analogous + * {@code @ResourceLock(value, mode)} declaration. * - * @param testClass a test class to add shared resources + * @param testClass a test class for which to add shared resources * @return a set of {@link Lock}; may be empty */ default Set provideForClass(Class testClass) { @@ -62,67 +61,81 @@ default Set provideForClass(Class testClass) { } /** - * Add shared resources to a {@linkplain Nested nested} test class. + * Add shared resources for a + * {@link org.junit.jupiter.api.Nested @Nested} test class. * *

Invoked in case: *

    - *
  • an enclosing test class of any level or its parent class - * is annotated with {@code @ResourceLock(providers)}.
  • - *
  • a nested test class or its parent class - * is annotated with {@code @ResourceLock(providers)}.
  • + *
  • an enclosing test class of any level or its parent class is + * annotated with {@code @ResourceLock(providers = ...)}.
  • + *
  • a nested test class or its parent class is annotated with + * {@code @ResourceLock(providers = ...)}.
  • *
* - *

Invoked for a nested test class - * annotated with {@code @ResourceLock(providers)} - * and for its child classes. + * @apiNote Adding {@linkplain Lock a shared resource} via this method has + * the same semantics as annotating a nested test class with an analogous + * {@code @ResourceLock(value, mode)} declaration. * - * @apiNote Adding {@linkplain Lock a shared resource} with this method - * has the same semantics as annotating a nested test class - * with analogous {@code @ResourceLock(value, mode)}. + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. * - * @param testClass a nested test class to add shared resources + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} + * @param testClass a nested test class for which to add shared resources * @return a set of {@link Lock}; may be empty - * @see Nested + * @see org.junit.jupiter.api.Nested @Nested */ - default Set provideForNestedClass(Class testClass) { + default Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return emptySet(); } /** - * Add shared resources to a test method. + * Add shared resources for a test method. * *

Invoked in case: *

    - *
  • an enclosing test class of any level or its parent class - * is annotated with {@code @ResourceLock(providers)}.
  • - *
  • a test method - * is annotated with {@code @ResourceLock(providers)}.
  • + *
  • an enclosing test class of any level or its parent class is + * annotated with {@code @ResourceLock(providers)}.
  • + *
  • a test method is annotated with {@code @ResourceLock(providers)}.
  • *
* * @apiNote Adding {@linkplain Lock a shared resource} with this method * has the same semantics as annotating a test method * with analogous {@code @ResourceLock(value, mode)}. * - * @param testClass a test class - * or {@linkplain Nested nested} test class containing the {@code testMethod} - * @param testMethod a test method to add shared resources + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. Similarly, the class instance + * supplied as {@code testClass} may differ from the class returned by + * {@code testMethod.getDeclaringClass()} — for example, when a test + * method is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} + * @param testClass the test class or {@link org.junit.jupiter.api.Nested @Nested} + * test class that contains the {@code testMethod} + * @param testMethod a test method for which to add shared resources * @return a set of {@link Lock}; may be empty + * @see org.junit.jupiter.api.Nested @Nested */ - default Set provideForMethod(Class testClass, Method testMethod) { + default Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { return emptySet(); } /** + * {@code Lock} represents a shared resource. * - *

{@link Lock} represents a shared resource. - * - *

Each resource is identified by a {@link #key}. - * In addition, the {@link #accessMode} allows to specify - * whether a test class or test - * method requires {@link ResourceAccessMode#READ_WRITE READ_WRITE} - * or only {@link ResourceAccessMode#READ READ} access to the resource. + *

Each resource is identified by a {@linkplain #getKey() key}. In addition, + * the {@linkplain #getAccessMode() access mode} allows one to specify whether + * a test class or test method requires {@link ResourceAccessMode#READ_WRITE + * READ_WRITE} or {@link ResourceAccessMode#READ READ} access to the resource. * - * @apiNote {@link Lock#key} and {@link Lock#accessMode} have the same + * @apiNote {@link #getKey()} and {@link #getAccessMode()} have the same * semantics as {@link ResourceLock#value()} and {@link ResourceLock#mode()} * respectively. * @@ -140,7 +153,7 @@ final class Lock { private final ResourceAccessMode accessMode; /** - * Create a new {@code Lock} with {@code accessMode = READ_WRITE}. + * Create a new {@code Lock} with {@link ResourceAccessMode#READ_WRITE}. * * @param key the identifier of the resource; never {@code null} or blank * @see ResourceLock#value() @@ -153,7 +166,8 @@ public Lock(String key) { * Create a new {@code Lock}. * * @param key the identifier of the resource; never {@code null} or blank - * @param accessMode the lock mode to use to synchronize access to the resource; never {@code null} + * @param accessMode the lock mode to use to synchronize access to the + * resource; never {@code null} * @see ResourceLock#value() * @see ResourceLock#mode() */ @@ -163,21 +177,21 @@ public Lock(String key, ResourceAccessMode accessMode) { } /** - * Get the key of this lock. + * Get the key for this lock. * * @see ResourceLock#value() */ public String getKey() { - return key; + return this.key; } /** - * Get the access mode of this lock. + * Get the access mode for this lock. * * @see ResourceLock#mode() */ public ResourceAccessMode getAccessMode() { - return accessMode; + return this.accessMode; } @Override @@ -188,20 +202,20 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - Lock lock = (Lock) o; - return Objects.equals(key, lock.key) && accessMode == lock.accessMode; + Lock that = (Lock) o; + return this.key.equals(that.key) && this.accessMode == that.accessMode; } @Override public int hashCode() { - return Objects.hash(key, accessMode); + return Objects.hash(this.key, this.accessMode); } @Override public String toString() { return new ToStringBuilder(this) // - .append("key", key) // - .append("accessMode", accessMode) // + .append("key", this.key) // + .append("accessMode", this.accessMode) // .toString(); } } 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 bf7cb5d9a059..fd9b6dae734a 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 @@ -192,6 +192,16 @@ public Store getStore(Namespace namespace) { return new NamespaceAwareStore(this.valuesStore, namespace); } + @Override + public Store getSessionLevelStore() { + return getStore(Namespace.create("session")); + } + + @Override + public Store getRequestLevelStore() { + return getStore(Namespace.create("request")); + } + @Override public Set getTags() { // return modifiable copy diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index 7c2a5571694c..e935db38e782 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -79,8 +80,8 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForClass(getTestClass()); + public Function> getResourceLocksProviderEvaluator() { + return provider -> provider.provideForClass(getTestClass()); } } 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 ebe3d127bf0c..76b65ef5e49a 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 @@ -14,6 +14,7 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; @@ -89,10 +90,10 @@ static String determineDisplayName(AnnotatedElement element, Supplier di return displayNameSupplier.get(); } - static String determineDisplayNameForMethod(Class testClass, Method testMethod, - JupiterConfiguration configuration) { + static String determineDisplayNameForMethod(Supplier>> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { return determineDisplayName(testMethod, - createDisplayNameSupplierForMethod(testClass, testMethod, configuration)); + createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration)); } static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { @@ -100,16 +101,16 @@ static Supplier createDisplayNameSupplierForClass(Class testClass, Ju generator -> generator.generateDisplayNameForClass(testClass)); } - static Supplier createDisplayNameSupplierForNestedClass(Class testClass, - JupiterConfiguration configuration) { + static Supplier createDisplayNameSupplierForNestedClass(Supplier>> enclosingInstanceTypes, + Class testClass, JupiterConfiguration configuration) { return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForNestedClass(testClass)); + generator -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes.get(), testClass)); } - private static Supplier createDisplayNameSupplierForMethod(Class testClass, Method testMethod, - JupiterConfiguration configuration) { + private static Supplier createDisplayNameSupplierForMethod(Supplier>> enclosingInstanceTypes, + Class testClass, Method testMethod, JupiterConfiguration configuration) { return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForMethod(testClass, testMethod)); + generator -> generator.generateDisplayNameForMethod(enclosingInstanceTypes.get(), testClass, testMethod)); } private static Supplier createDisplayNameSupplier(Class testClass, JupiterConfiguration configuration, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 525b58293709..3a5b785591f8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -13,14 +13,18 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; +import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; @@ -59,9 +63,9 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor im private final Set tags; MethodBasedTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod, - configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration), + testClass, testMethod, configuration); } MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, @@ -96,8 +100,18 @@ public ExclusiveResourceCollector getExclusiveResourceCollector() { } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForMethod(getTestClass(), getTestMethod()); + public Function> getResourceLocksProviderEvaluator() { + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, + (provider, enclosingInstanceTypes) -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), + getTestMethod())); + } + + private List> getEnclosingTestClasses() { + return getParent() // + .filter(ClassBasedTestDescriptor.class::isInstance) // + .map(ClassBasedTestDescriptor.class::cast) // + .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // + .orElseGet(Collections::emptyList); } @Override 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 6d4dc1e2d088..b0619fa09cff 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 @@ -13,12 +13,15 @@ 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; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; @@ -46,8 +49,10 @@ public class NestedClassTestDescriptor extends ClassBasedTestDescriptor { public static final String SEGMENT_TYPE = "nested-class"; - public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, JupiterConfiguration configuration) { - super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration); + public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, + createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration); } // --- TestDescriptor ------------------------------------------------------ @@ -62,7 +67,11 @@ public final Set getTags() { @Override public List> getEnclosingTestClasses() { - TestDescriptor parent = getParent().orElse(null); + return getEnclosingTestClasses(getParent().orElse(null)); + } + + @API(status = INTERNAL, since = "5.12") + public static List> getEnclosingTestClasses(TestDescriptor parent) { if (parent instanceof ClassBasedTestDescriptor) { ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent; List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); @@ -87,8 +96,9 @@ protected TestInstances instantiateTestClass(JupiterEngineExecutionContext paren } @Override - public Set evaluateResourceLocksProvider(ResourceLocksProvider provider) { - return provider.provideForNestedClass(getTestClass()); + public Function> getResourceLocksProviderEvaluator() { + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, + enclosingInstanceTypes) -> provider.provideForNestedClass(enclosingInstanceTypes, getTestClass())); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java index e7aa88edea52..c4f427989036 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -14,7 +14,11 @@ import java.util.ArrayDeque; import java.util.Deque; +import java.util.List; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.parallel.ResourceLocksProvider; @@ -35,8 +39,10 @@ default Stream determineExclusiveResources() { parent = parent.getParent().orElse(null); } + Function> evaluator = getResourceLocksProviderEvaluator(); + if (ancestors.isEmpty()) { - return determineOwnExclusiveResources(); + return determineOwnExclusiveResources(evaluator); } Stream parentStaticResourcesForChildren = ancestors.getLast() // @@ -44,18 +50,37 @@ default Stream determineExclusiveResources() { Stream ancestorDynamicResources = ancestors.stream() // .map(ResourceLockAware::getExclusiveResourceCollector) // - .flatMap(collector -> collector.getDynamicResources(this::evaluateResourceLocksProvider)); + .flatMap(collector -> collector.getDynamicResources(evaluator)); - return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, determineOwnExclusiveResources())// + return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, + determineOwnExclusiveResources(evaluator))// .flatMap(s -> s); } - default Stream determineOwnExclusiveResources() { - return this.getExclusiveResourceCollector().getAllExclusiveResources(this::evaluateResourceLocksProvider); + default Stream determineOwnExclusiveResources( + Function> providerToLocks) { + return this.getExclusiveResourceCollector().getAllExclusiveResources(providerToLocks); } ExclusiveResourceCollector getExclusiveResourceCollector(); - Set evaluateResourceLocksProvider(ResourceLocksProvider provider); + Function> getResourceLocksProviderEvaluator(); + + static Function> enclosingInstanceTypesDependentResourceLocksProviderEvaluator( + Supplier>> enclosingInstanceTypesSupplier, + BiFunction>, Set> evaluator) { + return new Function>() { + + private List> enclosingInstanceTypes; + + @Override + public Set apply(ResourceLocksProvider provider) { + if (this.enclosingInstanceTypes == null) { + this.enclosingInstanceTypes = enclosingInstanceTypesSupplier.get(); + } + return evaluator.apply(provider, this.enclosingInstanceTypes); + } + }; + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index e4643baa54de..f0d37814bbf2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.net.URI; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Stream; @@ -63,8 +64,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index 423f2e3b9013..67fa136a52d1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -17,6 +17,8 @@ import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -75,8 +77,8 @@ public class TestMethodTestDescriptor extends MethodBasedTestDescriptor { private final ReflectiveInterceptorCall interceptorCall; public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); this.interceptorCall = defaultInterceptorCall; } 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 f89c17b26a32..e592ba3a6326 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 @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -46,8 +47,8 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implem private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, templateMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration); } // --- Filterable ---------------------------------------------------------- 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 46d97088c358..0f809dcbde47 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 @@ -12,6 +12,7 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; +import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.support.ReflectionSupport.findMethods; @@ -122,9 +123,9 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class< } private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { - return new NestedClassTestDescriptor( - parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass, - configuration); + UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, + testClass.getSimpleName()); + return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration); } private Resolution toResolution(Optional testDescriptor) { 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 7f76e2d30fce..9d5af96aa103 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 @@ -166,8 +166,8 @@ private enum MethodType { TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestMethodTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, configuration); } }, @@ -176,8 +176,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestFactoryTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, + configuration); } }, @@ -185,8 +186,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestTemplateTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, + configuration); } }; @@ -207,7 +209,7 @@ private Optional resolve(List> enclosingClasses, Class< } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // parent -> Optional.of( - createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); + createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { @@ -227,7 +229,7 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq // @formatter:off return methodFinder.findMethod(methodSpecPart, testClass) .filter(methodPredicate) - .map(method -> createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)); + .map(method -> createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration)); // @formatter:on }); } @@ -237,6 +239,12 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq return Optional.empty(); } + private TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, Method method, + JupiterConfiguration configuration) { + UniqueId uniqueId = createUniqueId(method, parent); + return createTestDescriptor(uniqueId, testClass, method, parent::getEnclosingTestClasses, configuration); + } + private UniqueId createUniqueId(Method method, TestDescriptor parent) { String methodId = String.format("%s(%s)", method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); @@ -244,7 +252,7 @@ private UniqueId createUniqueId(Method method, TestDescriptor parent) { } protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration); } 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 b35dc58c20d8..357edb731c6e 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 @@ -19,6 +19,7 @@ import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; +import static org.junit.platform.commons.util.ReflectionUtils.isRecordObject; import java.io.File; import java.io.IOException; @@ -135,7 +136,9 @@ private void injectStaticFields(ExtensionContext context, Class testClass) { } private void injectInstanceFields(ExtensionContext context, Object instance) { - injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic); + if (!isRecordObject(instance)) { + injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic); + } } private void injectFields(ExtensionContext context, Object testInstance, Class testClass, diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index 700fcb3d53fc..5f8d2cbeffc4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -116,7 +116,7 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } /** - * Tries to get the {@linkplain Resource resources} for the supplied classpath + * Try to get the {@linkplain Resource resources} for the supplied classpath * resource name. * *

The name of a classpath resource must follow the semantics @@ -130,7 +130,8 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) * @return a successful {@code Try} containing the loaded resources or a failed * {@code Try} containing the exception if no such resources could be loaded; * never {@code null} - * @since 1.11 + * @since 1.12 + * @see #tryToGetResources(String, ClassLoader) */ @API(status = EXPERIMENTAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName) { @@ -138,7 +139,7 @@ public static Try> tryToGetResources(String classpathResourceName) } /** - * Tries to load the {@linkplain Resource resources} for the supplied classpath + * Try to load the {@linkplain Resource resources} for the supplied classpath * resource name, using the supplied {@link ClassLoader}. * *

The name of a classpath resource must follow the semantics @@ -153,7 +154,8 @@ public static Try> tryToGetResources(String classpathResourceName) * @return a successful {@code Try} containing the loaded resources or a failed * {@code Try} containing the exception if no such resources could be loaded; * never {@code null} - * @since 1.11 + * @since 1.12 + * @see #tryToGetResources(String) */ @API(status = EXPERIMENTAL, since = "1.12") public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { @@ -201,7 +203,6 @@ public static List> findAllClassesInClasspathRoot(URI root, Predicate findAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter); } @@ -248,7 +249,6 @@ public static Stream> streamAllClassesInClasspathRoot(URI root, Predica */ @API(status = EXPERIMENTAL, since = "1.11") public static Stream streamAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter); } @@ -296,7 +296,6 @@ public static List> findAllClassesInPackage(String basePackageName, Pre */ @API(status = EXPERIMENTAL, since = "1.11") public static List findAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter); } @@ -370,6 +369,7 @@ public static Stream streamAllResourcesInPackage(String basePackageNam * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInPackage(String, Predicate, Predicate) */ + @API(status = MAINTAINED, since = "1.1.1") public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { @@ -394,7 +394,6 @@ public static List> findAllClassesInModule(String moduleName, Predicate */ @API(status = EXPERIMENTAL, since = "1.11") public static List findAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter); } @@ -441,7 +440,6 @@ public static Stream> streamAllClassesInModule(String moduleName, Predi */ @API(status = EXPERIMENTAL, since = "1.11") public static Stream streamAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter); } @@ -711,10 +709,10 @@ public static Stream> streamNestedClasses(Class clazz, PredicateThe pattern intentionally captures the last bracket with the - * capital letter so that the combination can be looked up via - * {@link #classNameToTypeMap}. For example, the last matched group - * will contain {@code "[I"} instead of {@code "I"}. - * - * @see Class#getName() - */ - private static final Pattern VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN = Pattern.compile("^(\\[+)(\\[[ZBCDFIJS])$"); - // Pattern: "java.lang.String[]", "int[]", "int[][][][]", etc. // ?> => non-capturing atomic group // ++ => possessive quantifier @@ -263,7 +248,6 @@ public enum HierarchyTraversalMode { primitivesToWrappers.put(long.class, Long.class); primitivesToWrappers.put(float.class, Float.class); primitivesToWrappers.put(double.class, Double.class); - primitivesToWrappers.put(void.class, Void.class); primitiveToWrapperMap = Collections.unmodifiableMap(primitivesToWrappers); } @@ -381,6 +365,25 @@ public static boolean isInnerClass(Class clazz) { return !isStatic(clazz) && clazz.isMemberClass(); } + /** + * {@return whether the supplied {@code object} is an instance of a record class} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static boolean isRecordObject(Object object) { + return object != null && isRecordClass(object.getClass()); + } + + /** + * {@return whether the supplied {@code clazz} is a record class} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static boolean isRecordClass(Class clazz) { + Class superclass = clazz.getSuperclass(); + return superclass != null && "java.lang.Record".equals(superclass.getName()); + } + /** * Determine if the return type of the supplied method is primitive {@code void}. * @@ -419,8 +422,7 @@ public static boolean isMultidimensionalArray(Object obj) { * *

In contrast to {@link Class#isAssignableFrom(Class)}, this method * returns {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied source type. In addition, this method - * also supports + * wrapper matches the supplied source type. In addition, this method also supports * * widening conversions for primitive target types. * @@ -454,9 +456,8 @@ public static boolean isAssignableTo(Class sourceType, Class targetType) { * type for the purpose of reflective method invocations. * *

In contrast to {@link Class#isInstance(Object)}, this method returns - * {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied object's type. In addition, this method - * also supports + * {@code true} if the target type represents a primitive type whose wrapper + * matches the supplied object's type. In addition, this method also supports * * widening conversions for primitive types and their corresponding * wrapper types. @@ -858,32 +859,8 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } return Try.call(() -> { - Matcher matcher; - - // Primitive arrays such as "[I", "[[[[D", etc. - matcher = VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - - // Object arrays such as "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - matcher = VM_INTERNAL_OBJECT_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. - matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); + Matcher matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); if (matcher.matches()) { String componentTypeName = matcher.group(1); String bracketPairs = matcher.group(2); @@ -2011,7 +1988,7 @@ public static T makeAccessible(T executable) { return executable; } - @API(status = INTERNAL, since = "1.11") + @API(status = INTERNAL, since = "1.12") @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 public static Field makeAccessible(Field field) { if ((!isPublic(field) || !isPublic(field.getDeclaringClass()) || isFinal(field)) && !field.isAccessible()) { 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 3d320a0d1c74..34bceb6497db 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 @@ -10,25 +10,20 @@ package org.junit.platform.engine; -import static org.apiguardian.api.API.Status.DEPRECATED; -import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * Provides a single {@link TestEngine} access to the information necessary to * execute its tests. * *

A request contains an engine's root {@link TestDescriptor}, the - * {@link EngineExecutionListener} to be notified of test execution events, the + * {@link EngineExecutionListener} to be notified of test execution events, and * {@link ConfigurationParameters} that the engine may use to influence test - * execution, and an {@link OutputDirectoryProvider} for writing reports and - * other output files. + * execution. * * @since 1.0 * @see TestEngine @@ -39,23 +34,15 @@ public class ExecutionRequest { private final TestDescriptor rootTestDescriptor; private final EngineExecutionListener engineExecutionListener; private final ConfigurationParameters configurationParameters; - private final OutputDirectoryProvider outputDirectoryProvider; + // TODO[#4252] get store from constructor + private final NamespacedHierarchicalStore store = new NamespacedHierarchicalStore<>(null); - @Deprecated - @API(status = DEPRECATED, since = "1.11") + @API(status = INTERNAL, since = "1.0") public ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { - this(rootTestDescriptor, engineExecutionListener, configurationParameters, null); - } - - private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, - ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { - this.rootTestDescriptor = Preconditions.notNull(rootTestDescriptor, "rootTestDescriptor must not be null"); - this.engineExecutionListener = Preconditions.notNull(engineExecutionListener, - "engineExecutionListener must not be null"); - this.configurationParameters = Preconditions.notNull(configurationParameters, - "configurationParameters must not be null"); - this.outputDirectoryProvider = outputDirectoryProvider; + this.rootTestDescriptor = rootTestDescriptor; + this.engineExecutionListener = engineExecutionListener; + this.configurationParameters = configurationParameters; } /** @@ -68,41 +55,16 @@ private ExecutionRequest(TestDescriptor rootTestDescriptor, EngineExecutionListe * engine may use to influence test execution * @return a new {@code ExecutionRequest}; never {@code null} * @since 1.9 - * @deprecated Use {@link #create(TestDescriptor, EngineExecutionListener, ConfigurationParameters, OutputDirectoryProvider)} */ - @Deprecated - @API(status = DEPRECATED, since = "1.11") + @API(status = STABLE, since = "1.9") public static ExecutionRequest create(TestDescriptor rootTestDescriptor, EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters) { return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters); } /** - * Factory for creating an execution request. - * - * @param rootTestDescriptor the engine's root {@link TestDescriptor}; never - * {@code null} - * @param engineExecutionListener the {@link EngineExecutionListener} to be - * notified of test execution events; never {@code null} - * @param configurationParameters {@link ConfigurationParameters} that the - * engine may use to influence test execution; never {@code null} - * @param outputDirectoryProvider {@link OutputDirectoryProvider} for - * writing reports and other output files; never {@code null} - * @return a new {@code ExecutionRequest}; never {@code null} - * @since 1.12 - */ - @API(status = INTERNAL, since = "1.12") - public static ExecutionRequest create(TestDescriptor rootTestDescriptor, - EngineExecutionListener engineExecutionListener, ConfigurationParameters configurationParameters, - OutputDirectoryProvider outputDirectoryProvider) { - - return new ExecutionRequest(rootTestDescriptor, engineExecutionListener, configurationParameters, - Preconditions.notNull(outputDirectoryProvider, "outputDirectoryProvider must not be null")); - } - - /** - * {@return the root {@link TestDescriptor} of the engine that processes this - * request} + * Get the root {@link TestDescriptor} of the engine that processes this + * request. * *

Note: the root descriptor is the * {@code TestDescriptor} returned by @@ -113,33 +75,23 @@ public TestDescriptor getRootTestDescriptor() { } /** - * {@return the {@link EngineExecutionListener} to be notified of test execution - * events} + * Get the {@link EngineExecutionListener} to be notified of test execution + * events. */ public EngineExecutionListener getEngineExecutionListener() { return this.engineExecutionListener; } /** - * {@return the {@link ConfigurationParameters} that the engine may use to - * influence test execution} + * Get the {@link ConfigurationParameters} that the engine may use to + * influence test execution. */ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } - /** - * {@return the {@link OutputDirectoryProvider} for this request for writing - * reports and other output files} - * - * @throws PreconditionViolationException if the output directory provider - * is not available - * @since 1.12 - */ - @API(status = EXPERIMENTAL, since = "1.12") - public OutputDirectoryProvider getOutputDirectoryProvider() { - return Preconditions.notNull(outputDirectoryProvider, - "No OutputDirectoryProvider was configured for this request"); + public NamespacedHierarchicalStore getStore() { + return this.store; } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java new file mode 100644 index 000000000000..7fe2d37cb2f8 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Namespace.java @@ -0,0 +1,85 @@ +/* + * 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.STABLE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; + +@API(status = STABLE, since = "5.13") +public class Namespace { + + /** + * The default, global namespace which allows access to stored data from + * all extensions. + */ + public static final Namespace GLOBAL = Namespace.create(new Object()); + + /** + * Create a namespace which restricts access to data to all extensions + * which use the same sequence of {@code parts} for creating a namespace. + * + *

The order of the {@code parts} is significant. + * + *

Internally the {@code parts} are compared using {@link Object#equals(Object)}. + */ + public static Namespace create(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + return new Namespace(new ArrayList<>(Arrays.asList(parts))); + } + + private final List parts; + + private Namespace(List parts) { + this.parts = parts; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Namespace that = (Namespace) o; + return this.parts.equals(that.parts); + } + + @Override + public int hashCode() { + return this.parts.hashCode(); + } + + /** + * Create a new namespace by appending the supplied {@code parts} to the + * existing sequence of parts in this namespace. + * + * @return new namespace; never {@code null} + * @since 5.8 + */ + @API(status = STABLE, since = "5.13") + public Namespace append(Object... parts) { + Preconditions.notEmpty(parts, "parts array must not be null or empty"); + Preconditions.containsNoNullElements(parts, "individual parts must not be null"); + ArrayList newParts = new ArrayList<>(this.parts.size() + parts.length); + newParts.addAll(this.parts); + Collections.addAll(newParts, parts); + return new Namespace(newParts); + } +} 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 e359547a0e30..25f17d67ca92 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 @@ -29,15 +29,14 @@ import org.junit.platform.engine.DiscoverySelectorIdentifier; /** - * A {@link DiscoverySelector} that selects a nested {@link Method} - * or a combination of enclosing classes names, class name, method - * name, and parameter types so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on methods. + * A {@link DiscoverySelector} that selects a nested {@link Method} or a + * combination of enclosing class names, class name, method name, and parameter + * types so that {@link org.junit.platform.engine.TestEngine TestEngines} can + * discover tests or containers based on methods. * *

If a Java {@link Method} is provided, the selector will return that * {@linkplain #getMethod() method} and its method name, class name, enclosing - * classes names, and parameter types accordingly. If class names or method names + * class names, and parameter types accordingly. If class names or method names * are provided, this selector will only attempt to lazily load a class or method * if {@link #getEnclosingClasses()}, {@link #getNestedClass()}, * {@link #getMethod()}, or {@link #getParameterTypes()} is invoked. 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 9167dde42b13..6b5d1af79af7 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 @@ -17,6 +17,7 @@ import java.util.Comparator; import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; @@ -85,6 +86,18 @@ public NamespacedHierarchicalStore newChild() { return new NamespacedHierarchicalStore<>(this, this.closeAction); } + /** + * Returns the parent store of this {@code NamespacedHierarchicalStore}. + * + *

If this store does not have a parent, an empty {@code Optional} is returned. + * + * @return an {@code Optional} containing the parent store, or an empty {@code Optional} if there is no parent + * @since 5.13 + */ + public Optional> getParent() { + return Optional.ofNullable(this.parentStore); + } + /** * Determine if this store has been {@linkplain #close() closed}. * 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 3b46078278ca..6c4e05176cd8 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 @@ -13,6 +13,8 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** * The {@code Launcher} API is the main entry point for client code that @@ -127,4 +129,5 @@ public interface Launcher { @API(status = STABLE, since = "1.4") void execute(TestPlan testPlan, TestExecutionListener... listeners); + NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java index c78ed7761888..782607dd54b2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -13,6 +13,8 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.core.LauncherFactory; /** @@ -47,4 +49,6 @@ public interface LauncherSession extends AutoCloseable { @Override void close(); + NamespacedHierarchicalStore getStore(); + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 5ebc4f9a638e..cbdc9023896d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -17,10 +17,13 @@ import java.util.Collection; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.Namespace; import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; @@ -41,6 +44,8 @@ class DefaultLauncher implements Launcher { private final EngineExecutionOrchestrator executionOrchestrator = new EngineExecutionOrchestrator( listenerRegistry.testExecutionListeners); private final EngineDiscoveryOrchestrator discoveryOrchestrator; + private final LauncherSession launcherSession; + private final NamespacedHierarchicalStore sessionStore; /** * Construct a new {@code DefaultLauncher} with the supplied test engines. @@ -50,7 +55,8 @@ class DefaultLauncher implements Launcher { * @param postDiscoveryFilters the additional post discovery filters for * discovery requests; never {@code null} */ - DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters) { + DefaultLauncher(Iterable testEngines, Collection postDiscoveryFilters, + LauncherConfig config) { Preconditions.condition(testEngines != null && testEngines.iterator().hasNext(), () -> "Cannot create Launcher without at least one TestEngine; " + "consider adding an engine implementation JAR to the classpath"); @@ -59,6 +65,8 @@ class DefaultLauncher implements Launcher { "PostDiscoveryFilter array must not contain null elements"); this.discoveryOrchestrator = new EngineDiscoveryOrchestrator(testEngines, unmodifiableCollection(postDiscoveryFilters), listenerRegistry.launcherDiscoveryListeners); + this.launcherSession = LauncherFactory.openSession(config); + this.sessionStore = launcherSession.getStore(); } @Override @@ -94,6 +102,11 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return new NamespacedHierarchicalStore<>(this.sessionStore); + } + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, EngineDiscoveryOrchestrator.Phase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); 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 018eb41cf8a5..09df7277989e 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 @@ -14,6 +14,8 @@ import java.util.function.Supplier; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -43,6 +45,7 @@ public void close() { private final LauncherInterceptor interceptor; private final LauncherSessionListener listener; private final DelegatingLauncher launcher; + private final NamespacedHierarchicalStore store; DefaultLauncherSession(List interceptors, Supplier listenerSupplier, Supplier launcherSupplier) { @@ -58,6 +61,7 @@ public void close() { } this.launcher = new DelegatingLauncher(launcher); listener.launcherSessionOpened(this); + this.store = new NamespacedHierarchicalStore<>(null); } @Override @@ -78,6 +82,11 @@ public void close() { } } + @Override + public NamespacedHierarchicalStore getStore() { + return store; + } + private static class ClosedLauncher implements Launcher { static final ClosedLauncher INSTANCE = new ClosedLauncher(); @@ -109,6 +118,11 @@ public void execute(LauncherDiscoveryRequest launcherDiscoveryRequest, TestExecu public void execute(TestPlan testPlan, TestExecutionListener... listeners) { throw new PreconditionViolationException("Launcher session has already been closed"); } + + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + 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..297f85e02ee2 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 @@ -10,6 +10,8 @@ package org.junit.platform.launcher.core; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -52,4 +54,9 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { delegate.execute(testPlan, listeners); } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + return delegate.getStore(launcherDiscoveryRequest); + } + } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java index c756f27351f0..c6cdb93d7c2d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -134,7 +134,7 @@ private static DefaultLauncher createDefaultLauncher(LauncherConfig config, Set engines = collectTestEngines(config); List filters = collectPostDiscoveryFilters(config); - DefaultLauncher launcher = new DefaultLauncher(engines, filters); + DefaultLauncher launcher = new DefaultLauncher(engines, filters, config); registerLauncherDiscoveryListeners(config, launcher); registerTestExecutionListeners(config, launcher, configurationParameters); 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 dffde014867a..1aaa71af400a 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 @@ -13,6 +13,8 @@ import java.util.List; import java.util.function.Supplier; +import org.junit.platform.engine.Namespace; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.Launcher; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -71,6 +73,13 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { } } + @Override + public NamespacedHierarchicalStore getStore(LauncherDiscoveryRequest launcherDiscoveryRequest) { + try (LauncherSession session = createSession()) { + return session.getLauncher().getStore(launcherDiscoveryRequest); + } + } + private LauncherSession createSession() { LauncherSession session = new DefaultLauncherSession(interceptorFactory.get(), sessionListenerSupplier, launcherSupplier); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java index 8dbea0cdb42d..c7a1fe90efc6 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.EmptyStackException; +import java.util.List; import java.util.Stack; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; @@ -197,6 +198,23 @@ void indicativeSentencesGenerationInheritance() { ); } + @Test + void indicativeSentencesRuntimeEnclosingType() { + check(IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.class, // + "CONTAINER: Scenario 1", // + "CONTAINER: Scenario 1 -> Level 1", // + "CONTAINER: Scenario 1 -> Level 1 -> Level 2", // + "TEST: Scenario 1 -> Level 1 -> Level 2 -> this is a test"// + ); + + check(IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.class, // + "CONTAINER: Scenario 2", // + "CONTAINER: Scenario 2 -> Level 1", // + "CONTAINER: Scenario 2 -> Level 1 -> Level 2", // + "TEST: Scenario 2 -> Level 1 -> Level 2 -> this is a test"// + ); + } + private void check(Class testClass, String... expectedDisplayNames) { var request = request().selectors(selectClass(testClass)).build(); var descriptors = discoverTests(request).getDescendants(); @@ -217,12 +235,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nn"; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return "nn"; } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java new file mode 100644 index 000000000000..927903c14612 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java @@ -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.api; + +/** + * @since 5.12 + */ +@DisplayName("Scenario 1") +class IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase + extends IndicativeSentencesRuntimeEnclosingTypeTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java new file mode 100644 index 000000000000..c6e2f377db10 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java @@ -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.api; + +/** + * @since 5.12 + */ +@DisplayName("Scenario 2") +class IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase + extends IndicativeSentencesRuntimeEnclosingTypeTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java new file mode 100644 index 000000000000..86244f4cbfcc --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java @@ -0,0 +1,33 @@ +/* + * 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; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * @since 5.12 + */ +@DisplayName("Base Scenario") +@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) +abstract class IndicativeSentencesRuntimeEnclosingTypeTestCase { + + @Nested + class Level_1 { + + @Nested + class Level_2 { + + @Test + void this_is_a_test() { + } + } + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java index 2025c6e0ad49..60e7c36b808f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -255,7 +256,7 @@ private ClassTestDescriptor getClassTestDescriptor(Class testClass) { private Set getMethodResources(Class testClass) { var descriptor = new TestMethodTestDescriptor( // - uniqueId, testClass, getDeclaredTestMethod(testClass), configuration // + uniqueId, testClass, getDeclaredTestMethod(testClass), List::of, configuration // ); descriptor.setParent(getClassTestDescriptor(testClass)); return descriptor.getExclusiveResources(); @@ -271,7 +272,7 @@ private static Method getDeclaredTestMethod(Class testClass) { } private Set getNestedClassResources(Class testClass) { - var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, configuration); + var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, List::of, configuration); descriptor.setParent(getClassTestDescriptor(testClass.getEnclosingClass())); return descriptor.getExclusiveResources(); } @@ -360,7 +361,8 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b1")); } } @@ -368,7 +370,8 @@ public Set provideForMethod(Class testClass, Method testMethod) { static class MethodLevelProvider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b2")); } } @@ -376,7 +379,7 @@ public Set provideForMethod(Class testClass, Method testMethod) { static class NestedClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c1"), new Lock("c2", ResourceAccessMode.READ)); } } @@ -416,12 +419,13 @@ public Set provideForClass(Class testClass) { static class SecondClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("b2", ResourceAccessMode.READ)); } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c2")); } } @@ -429,7 +433,7 @@ public Set provideForNestedClass(Class testClass) { static class NestedClassLevelProvider implements ResourceLocksProvider { @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { return Set.of(new Lock("c3")); } } @@ -451,7 +455,8 @@ void test() { static class Provider implements ResourceLocksProvider { @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return Set.of(new Lock("a1")); } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java index 8a73be42b8ee..f5bc7a17b77b 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -20,6 +20,7 @@ import static org.junit.platform.testkit.engine.EventConditions.test; import java.lang.reflect.Method; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -60,6 +61,12 @@ void methodLevelProviderInNestedClass() { assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); } + @Test + void providesAccessToRuntimeEnclosingInstances() { + var events = execute(SubClassLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + private Stream execute(Class testCase) { return executeTestsForClass(testCase).allEvents().stream(); } @@ -108,28 +115,35 @@ static class Provider implements ResourceLocksProvider { private static boolean isProvideForNestedClassCalled = false; private static boolean isProvideForNestedTestMethodCalled = false; + private Class testClass; + @Override public Set provideForClass(Class testClass) { + this.testClass = testClass; isProvideForClassCalled = true; - assertEquals(ClassLevelProviderTestCase.class, testClass); + assertThat(testClass).isAssignableTo(ClassLevelProviderTestCase.class); return emptySet(); } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; + assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals(ClassLevelProviderTestCase.NestedClass.class, testClass); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { - if (testClass == ClassLevelProviderTestCase.class) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + if (ClassLevelProviderTestCase.class.isAssignableFrom(testClass)) { + assertEquals(List.of(), enclosingInstanceTypes); assertEquals("test", testMethod.getName()); isProvideForTestMethodCalled = true; return emptySet(); } if (testClass == ClassLevelProviderTestCase.NestedClass.class) { + assertEquals(List.of(this.testClass), enclosingInstanceTypes); assertEquals("nestedTest", testMethod.getName()); isProvideForNestedTestMethodCalled = true; return emptySet(); @@ -140,6 +154,9 @@ public Set provideForMethod(Class testClass, Method testMethod) { } } + static class SubClassLevelProviderTestCase extends ClassLevelProviderTestCase { + } + @SuppressWarnings("JUnitMalformedDeclaration") static class NestedClassLevelProviderTestCase { @@ -177,15 +194,18 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { isProvideForNestedClassCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClass.class, testClass); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); assertEquals(NestedClassLevelProviderTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); @@ -226,14 +246,16 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(), enclosingInstanceTypes); assertEquals(MethodLevelProviderTestCase.class, testClass); assertEquals("test", testMethod.getName()); return emptySet(); @@ -274,14 +296,16 @@ public Set provideForClass(Class testClass) { } @Override - public Set provideForNestedClass(Class testClass) { + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { fail("'provideForNestedClass' should not be called"); return emptySet(); } @Override - public Set provideForMethod(Class testClass, Method testMethod) { + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { isProvideForMethodCalled = true; + assertEquals(List.of(MethodLevelProviderInNestedClassTestCase.class), enclosingInstanceTypes); assertEquals(MethodLevelProviderInNestedClassTestCase.NestedClass.class, testClass); assertEquals("nestedTest", testMethod.getName()); return emptySet(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java index f7b8afae102c..6b00ad5b9282 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; @@ -22,12 +23,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nested-class-display-name"; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return "method-display-name"; } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java index a5d019e1f95a..7a9ca9a4a165 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -70,7 +71,7 @@ void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() { @Nested class ClassDisplayNameSupplierTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { @@ -115,12 +116,12 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { @Nested class NestedClassDisplayNameTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, StandardDisplayNameTestCase.class, configuration); assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName()); @@ -129,7 +130,7 @@ void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { @Test void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); @@ -138,7 +139,7 @@ void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { @Test void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NullDisplayNameTestCase.NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); @@ -148,14 +149,14 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { @Nested class MethodDisplayNameTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Method method = MyTestCase.class.getDeclaredMethod("test1"); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(StandardDisplayNameTestCase.class, - method, configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, + StandardDisplayNameTestCase.class, method, configuration); assertThat(displayName).isEqualTo("test1()"); } @@ -165,8 +166,8 @@ void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception { Method method = MyTestCase.class.getDeclaredMethod("test1"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NotDisplayNameTestCase.class, method, - configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NotDisplayNameTestCase.class, + method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @@ -176,8 +177,8 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() throws Exc Method method = NullDisplayNameTestCase.class.getDeclaredMethod("test"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NullDisplayNameTestCase.class, method, - configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NullDisplayNameTestCase.class, + method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @@ -238,12 +239,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return null; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return null; } 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 482290a3c559..66796f302620 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 @@ -418,7 +418,7 @@ void configurationParameter(Function { var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow(); var methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); - var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, + var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, List::of, configuration); return new MethodExtensionContext(null, null, methodTestDescriptor, configuration, extensionRegistry, null); @@ -428,7 +428,7 @@ void configurationParameter(Function testClass = TestCase.class; Method testMethod = testClass.getDeclaredMethod("test"); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, List::of, configuration); assertEquals(uniqueId, descriptor.getUniqueId()); @@ -127,7 +127,7 @@ void constructFromMethodWithAnnotations() throws Exception { JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); Method testMethod = TestCase.class.getDeclaredMethod("foo"); TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); classDescriptor.addChild(methodDescriptor); assertEquals(testMethod, methodDescriptor.getTestMethod()); @@ -143,7 +143,7 @@ void constructFromMethodWithAnnotations() throws Exception { void constructFromMethodWithCustomTestAnnotation() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("custom name", descriptor.getDisplayName(), "display name:"); @@ -155,7 +155,7 @@ void constructFromMethodWithCustomTestAnnotation() throws Exception { void constructFromMethodWithParameters() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name"); @@ -166,7 +166,7 @@ void constructFromMethodWithParameters() throws Exception { void constructFromMethodWithPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[])", descriptor.getDisplayName(), "display name"); @@ -177,7 +177,7 @@ void constructFromMethodWithPrimitiveArrayParameter() throws Exception { void constructFromMethodWithObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[])", descriptor.getDisplayName(), "display name"); @@ -188,7 +188,7 @@ void constructFromMethodWithObjectArrayParameter() throws Exception { void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name"); @@ -199,7 +199,7 @@ void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exc void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name"); @@ -210,7 +210,7 @@ void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Except void constructFromInheritedMethod() throws Exception { Method testMethod = ConcreteTestCase.class.getMethod("theTest"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); @@ -230,7 +230,7 @@ void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() { assertEquals("class-display-name", descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals("nested-class-display-name", descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); @@ -249,7 +249,7 @@ void defaultDisplayNamesForTestClasses() { assertEquals(getClass().getSimpleName(), descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); @@ -269,7 +269,7 @@ void enclosingClassesAreDerivedFromParent() { ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, - configuration); + List::of, configuration); assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty(); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index 618d290870d2..6ad584b728df 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -18,6 +18,7 @@ import java.io.File; import java.lang.reflect.Method; import java.net.URI; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -147,7 +148,7 @@ void before() throws Exception { Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class, - testMethod, jupiterConfiguration); + testMethod, List::of, jupiterConfiguration); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java index 0491150abb57..65b2bf268688 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ void invocationsDoNotDeclareExclusiveResources() throws Exception { JupiterConfiguration configuration = mock(); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"), - testClass, testTemplateMethod, configuration); + testClass, testTemplateMethod, List::of, configuration); TestTemplateInvocationContext invocationContext = mock(); when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation"); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java index 1cd492db23ce..c0dab4d6a66e 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.DisplayNameGenerator; @@ -45,7 +46,7 @@ void inheritsTagsFromParent() throws Exception { TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), @@ -63,7 +64,7 @@ void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exce TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); @@ -80,7 +81,7 @@ void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exc TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java index 2deb303768b9..5eddab66bcad 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java @@ -164,6 +164,12 @@ void resolvesSeparateTempDirsForEachAnnotationDeclaration(TestInstance.Lifecycle assertThat(testATempDirs).doesNotContainEntry("afterEach2", testBTempDirs.get("afterEach2")); } + @Test + void supportsConstructorInjectionOnRecords() { + executeTestsForClass(TempDirRecordTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); + } + @Test @DisplayName("does not prevent constructor parameter resolution") void tempDirectoryDoesNotPreventConstructorParameterResolution() { @@ -1527,4 +1533,12 @@ void test(@SuppressWarnings("unused") @TempDir Path tempDir) { } + @SuppressWarnings("JUnitMalformedDeclaration") + record TempDirRecordTestCase(@TempDir Path tempDir) { + @Test + void shouldExists() { + assertTrue(Files.exists(tempDir)); + } + } + } 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 b001ee14bd76..6b89d7aaeb1d 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 @@ -23,7 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.function.Try.success; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.util.ReflectionUtils.findFields; @@ -286,6 +285,23 @@ void getInterfaceMethodIfPossible() throws Exception { assertThat(interfaceMethod.getDeclaringClass()).isEqualTo(Closeable.class); } + @Test + void isRecordObject() { + assertTrue(ReflectionUtils.isRecordObject(new SomeRecord(1))); + assertFalse(ReflectionUtils.isRecordObject(new ClassWithOneCustomConstructor(""))); + assertFalse(ReflectionUtils.isRecordObject(null)); + } + + @Test + void isRecordClass() { + assertTrue(ReflectionUtils.isRecordClass(SomeRecord.class)); + assertFalse(ReflectionUtils.isRecordClass(ClassWithOneCustomConstructor.class)); + assertFalse(ReflectionUtils.isRecordClass(Object.class)); + } + + record SomeRecord(int n) { + } + static class ClassWithVoidAndNonVoidMethods { void voidMethod() { @@ -561,7 +577,9 @@ void isAssignableTo() { // Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, int.class)); assertTrue(ReflectionUtils.isAssignableTo(Boolean.class, boolean.class)); - assertTrue(ReflectionUtils.isAssignableTo(Void.class, void.class)); + + // Void to void + assertFalse(ReflectionUtils.isAssignableTo(Void.class, void.class)); // Widening Conversions from Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, long.class)); @@ -802,73 +820,132 @@ void loadClass() { @Test void tryToLoadClass() { - assertThat(ReflectionUtils.tryToLoadClass(Integer.class.getName())).isEqualTo(success(Integer.class)); - assertThat(ReflectionUtils.tryToLoadClass(Void.class.getName())).isEqualTo(success(Void.class)); + assertTryToLoadClass(getClass().getName(), getClass()); + assertTryToLoadClass(Integer.class.getName(), Integer.class); + assertTryToLoadClass(Void.class.getName(), Void.class); } @Test void tryToLoadClassTrimsClassName() { - assertThat(ReflectionUtils.tryToLoadClass(" " + Integer.class.getName() + "\t"))// - .isEqualTo(success(Integer.class)); + assertTryToLoadClass(" " + Integer.class.getName() + "\t", Integer.class); } @Test - void tryToLoadClassForPrimitive() { - assertThat(ReflectionUtils.tryToLoadClass(int.class.getName())).isEqualTo(success(int.class)); - assertThat(ReflectionUtils.tryToLoadClass(void.class.getName())).isEqualTo(success(void.class)); + void tryToLoadClassForVoidPseudoPrimitiveType() { + assertTryToLoadClass("void", void.class); } @Test - void tryToLoadClassForPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[].class.getName())).isEqualTo(success(int[].class)); + void tryToLoadClassForPrimitiveType() { + assertTryToLoadClass("boolean", boolean.class); + assertTryToLoadClass("char", char.class); + assertTryToLoadClass("byte", byte.class); + assertTryToLoadClass("short", short.class); + assertTryToLoadClass("int", int.class); + assertTryToLoadClass("long", long.class); + assertTryToLoadClass("float", float.class); + assertTryToLoadClass("double", double.class); } @Test - void tryToLoadClassForPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[]")).isEqualTo(success(int[].class)); + void tryToLoadClassForBinaryPrimitiveArrayName() { + assertTryToLoadClass("[Z", boolean[].class); + assertTryToLoadClass("[C", char[].class); + assertTryToLoadClass("[B", byte[].class); + assertTryToLoadClass("[S", short[].class); + assertTryToLoadClass("[I", int[].class); + assertTryToLoadClass("[J", long[].class); + assertTryToLoadClass("[F", float[].class); + assertTryToLoadClass("[D", double[].class); } @Test - void tryToLoadClassForObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[].class.getName())).isEqualTo(success(String[].class)); + void tryToLoadClassForCanonicalPrimitiveArrayName() { + assertTryToLoadClass("boolean[]", boolean[].class); + assertTryToLoadClass("char[]", char[].class); + assertTryToLoadClass("byte[]", byte[].class); + assertTryToLoadClass("short[]", short[].class); + assertTryToLoadClass("int[]", int[].class); + assertTryToLoadClass("long[]", long[].class); + assertTryToLoadClass("float[]", float[].class); + assertTryToLoadClass("double[]", double[].class); } @Test - void tryToLoadClassForObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[]")).isEqualTo(success(String[].class)); + void tryToLoadClassForBinaryObjectArrayName() { + assertTryToLoadClass(String[].class.getName(), String[].class); } @Test - void tryToLoadClassForTwoDimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][].class.getName())).isEqualTo(success(int[][].class)); + void tryToLoadClassForCanonicalObjectArrayName() { + assertTryToLoadClass("java.lang.String[]", String[].class); } @Test - void tryToLoadClassForTwoDimensionaldimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][]")).isEqualTo(success(int[][].class)); + void tryToLoadClassForBinaryTwoDimensionalPrimitiveArrayName() { + assertTryToLoadClass("[[Z", boolean[][].class); + assertTryToLoadClass("[[C", char[][].class); + assertTryToLoadClass("[[B", byte[][].class); + assertTryToLoadClass("[[S", short[][].class); + assertTryToLoadClass("[[I", int[][].class); + assertTryToLoadClass("[[J", long[][].class); + assertTryToLoadClass("[[F", float[][].class); + assertTryToLoadClass("[[D", double[][].class); } @Test - void tryToLoadClassForMultidimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][][][][].class.getName()))// - .isEqualTo(success(int[][][][][].class)); + void tryToLoadClassForCanonicalTwoDimensionalPrimitiveArrayName() { + assertTryToLoadClass("boolean[][]", boolean[][].class); + assertTryToLoadClass("char[][]", char[][].class); + assertTryToLoadClass("byte[][]", byte[][].class); + assertTryToLoadClass("short[][]", short[][].class); + assertTryToLoadClass("int[][]", int[][].class); + assertTryToLoadClass("long[][]", long[][].class); + assertTryToLoadClass("float[][]", float[][].class); + assertTryToLoadClass("double[][]", double[][].class); } @Test - void tryToLoadClassForMultidimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][][][][]")).isEqualTo(success(int[][][][][].class)); + void tryToLoadClassForBinaryMultidimensionalPrimitiveArrayName() { + assertTryToLoadClass("[[[[[Z", boolean[][][][][].class); + assertTryToLoadClass("[[[[[C", char[][][][][].class); + assertTryToLoadClass("[[[[[B", byte[][][][][].class); + assertTryToLoadClass("[[[[[S", short[][][][][].class); + assertTryToLoadClass("[[[[[I", int[][][][][].class); + assertTryToLoadClass("[[[[[J", long[][][][][].class); + assertTryToLoadClass("[[[[[F", float[][][][][].class); + assertTryToLoadClass("[[[[[D", double[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[][][][][].class.getName()))// - .isEqualTo(success(String[][][][][].class)); + void tryToLoadClassForCanonicalMultidimensionalPrimitiveArrayName() { + assertTryToLoadClass("boolean[][][][][]", boolean[][][][][].class); + assertTryToLoadClass("char[][][][][]", char[][][][][].class); + assertTryToLoadClass("byte[][][][][]", byte[][][][][].class); + assertTryToLoadClass("short[][][][][]", short[][][][][].class); + assertTryToLoadClass("int[][][][][]", int[][][][][].class); + assertTryToLoadClass("long[][][][][]", long[][][][][].class); + assertTryToLoadClass("float[][][][][]", float[][][][][].class); + assertTryToLoadClass("double[][][][][]", double[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[][][][][]"))// - .isEqualTo(success(String[][][][][].class)); + void tryToLoadClassForBinaryMultidimensionalObjectArrayName() { + assertTryToLoadClass(String[][][][][].class.getName(), String[][][][][].class); + } + + @Test + void tryToLoadClassForCanonicalMultidimensionalObjectArrayName() { + assertTryToLoadClass("java.lang.String[][][][][]", String[][][][][].class); + } + + private static void assertTryToLoadClass(String name, Class type) { + try { + assertThat(ReflectionUtils.tryToLoadClass(name).get()).as(name).isEqualTo(type); + } + catch (Exception ex) { + ExceptionUtils.throwAsUncheckedException(ex); + } } } @@ -1596,7 +1673,6 @@ void findMethodsWithoutStaticHidingUsingHierarchyDownMode() throws Exception { var methods = findMethods(child, method -> true, TOP_DOWN); assertEquals(9, methods.size()); - methods.forEach(System.err::println); assertThat(methods.subList(0, 2)).containsOnly(ifcMethod1, ifcMethod2); assertThat(methods.subList(2, 6)).containsOnly(parentMethod1, parentMethod2, parentMethod4, parentMethod5); assertThat(methods.subList(6, 9)).containsOnly(childMethod1, childMethod4, childMethod5); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java index 4a7cce72c99e..417bb8119906 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java @@ -34,7 +34,7 @@ */ class JavaVersionsTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; @TempDir 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 dc9b99e58a8d..cb3eb217fa75 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 @@ -10,14 +10,47 @@ package platform.tooling.support.tests; +import static platform.tooling.support.tests.ManagedResource.Scope.GLOBAL; +import static platform.tooling.support.tests.ManagedResource.Scope.PER_CONTEXT; + import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +@ManagedResource.Scoped(LocalMavenRepo.ScopeProvider.class) public class LocalMavenRepo implements AutoCloseable { + public static class ScopeProvider implements ManagedResource.Scoped.Provider { + + private static final Namespace NAMESPACE = Namespace.create(LocalMavenRepo.class); + + @Override + public ManagedResource.Scope determineScope(ExtensionContext extensionContext) { + var store = extensionContext.getRoot().getStore(NAMESPACE); + var fileSystemType = store.getOrComputeIfAbsent("tempFileSystemType", key -> { + var type = getFileSystemType(Path.of(System.getProperty("java.io.tmpdir"))); + extensionContext.getRoot().publishReportEntry("tempFileSystemType", type); + return type; + }, String.class); + // Writing to the same file from multiple Maven processes may fail the build on Windows + return "NTFS".equalsIgnoreCase(fileSystemType) ? PER_CONTEXT : GLOBAL; + } + + private static String getFileSystemType(Path path) { + try { + return Files.getFileStore(path).type(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + private final Path tempDir; public LocalMavenRepo() { diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GlobalResource.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java similarity index 75% rename from platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GlobalResource.java rename to platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java index 8774adbda241..c278f0a8c0e8 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GlobalResource.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java @@ -32,15 +32,30 @@ @Target({ ElementType.PARAMETER, ElementType.FIELD }) @Retention(RUNTIME) -@ExtendWith(GlobalResource.Extension.class) -public @interface GlobalResource { +@ExtendWith(ManagedResource.Extension.class) +public @interface ManagedResource { + + @Target(ElementType.TYPE) + @Retention(RUNTIME) + @interface Scoped { + + Class value(); + + interface Provider { + Scope determineScope(ExtensionContext extensionContext); + } + } + + enum Scope { + GLOBAL, PER_CONTEXT + } class Extension implements ParameterResolver, TestInstancePostProcessor { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return parameterContext.isAnnotated(GlobalResource.class); + return parameterContext.isAnnotated(ManagedResource.class); } @Override @@ -50,9 +65,14 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte return getOrCreateResource(extensionContext, type).get(); } + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) { - streamFields(testInstance.getClass(), field -> AnnotationSupport.isAnnotated(field, GlobalResource.class), + streamFields(testInstance.getClass(), field -> AnnotationSupport.isAnnotated(field, ManagedResource.class), HierarchyTraversalMode.BOTTOM_UP) // .forEach(field -> { try { @@ -66,7 +86,16 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext extens @SuppressWarnings("unchecked") private Resource getOrCreateResource(ExtensionContext extensionContext, Class type) { - return extensionContext.getRoot().getStore(Namespace.GLOBAL) // + var scope = AnnotationSupport.findAnnotation(type, Scoped.class) // + .map(Scoped::value) // + .map(ReflectionSupport::newInstance) // + .map(provider -> provider.determineScope(extensionContext)) // + .orElse(Scope.GLOBAL); + var storingContext = switch (scope) { + case GLOBAL -> extensionContext.getRoot(); + case PER_CONTEXT -> extensionContext; + }; + return storingContext.getStore(Namespace.GLOBAL) // .getOrComputeIfAbsent(type, Resource::new, Resource.class); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index 9faf699942ea..5a795087abae 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -32,10 +32,10 @@ */ class MavenStarterTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; - @GlobalResource + @ManagedResource MavenRepoProxy mavenRepoProxy; @Test diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java index a59d90ecbf53..3a62853d52aa 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java @@ -33,7 +33,7 @@ */ class MavenSurefireCompatibilityTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; @ParameterizedTest diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java index c07954ac5c5e..4326fa6fa7c9 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java @@ -33,10 +33,10 @@ */ class MultiReleaseJarTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; - @GlobalResource + @ManagedResource MavenRepoProxy mavenRepoProxy; @Test diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java index a6467c511564..7869a3d59186 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java @@ -37,10 +37,10 @@ */ class UnalignedClasspathTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; - @GlobalResource + @ManagedResource MavenRepoProxy mavenRepoProxy; @ParameterizedTest diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java index 7f735753f009..2b95c5b891d5 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java @@ -29,7 +29,7 @@ class VintageMavenIntegrationTests { - @GlobalResource + @ManagedResource LocalMavenRepo localMavenRepo; @TempDir