diff --git a/.github/actions/main-build/action.yml b/.github/actions/main-build/action.yml index a7817cd314f0..bdadfb3a5b90 100644 --- a/.github/actions/main-build/action.yml +++ b/.github/actions/main-build/action.yml @@ -5,6 +5,10 @@ inputs: required: true description: Gradle arguments default: build + dependency-graph: + required: false + description: 'see https://github.com/gradle/gradle-build-action#enable-dependency-graph-generation-for-a-workflow' + default: disabled runs: using: "composite" steps: @@ -12,6 +16,7 @@ runs: - uses: ./.github/actions/run-gradle with: arguments: ${{ inputs.arguments }} + dependency-graph: ${{ inputs.dependency-graph }} - uses: actions/upload-artifact@v3 if: ${{ always() }} with: diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index a4b1bc129f6c..70b1cf718086 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -5,21 +5,26 @@ inputs: required: true description: Gradle arguments default: build + dependency-graph: + required: false + description: 'see https://github.com/gradle/gradle-build-action#enable-dependency-graph-generation-for-a-workflow' + default: disabled runs: using: "composite" steps: - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 id: setup-gradle-jdk with: distribution: temurin - java-version: 17 + java-version: 21 - uses: gradle/gradle-build-action@v2 env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} with: + dependency-graph: ${{ inputs.dependency-graph }} arguments: | -Porg.gradle.java.installations.auto-download=false - -Penterprise.predictiveTestSelection.enabled=${{ github.event_name == 'pull_request' }} + -Pdevelocity.predictiveTestSelection.enabled=${{ github.event_name == 'pull_request' }} "-Dscan.value.GitHub job=${{ github.job }}" javaToolchains ${{ inputs.arguments }} diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml index 4e8c96266c69..c9ea27fa7cd8 100644 --- a/.github/actions/setup-test-jdk/action.yml +++ b/.github/actions/setup-test-jdk/action.yml @@ -3,7 +3,7 @@ description: Sets up the JDK required to run platform-tooling-support-tests runs: using: "composite" steps: - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: distribution: temurin java-version: 8 diff --git a/.github/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml index ab8bb97a6f28..fef88e94d267 100644 --- a/.github/workflows/close-inactive-issues.yml +++ b/.github/workflows/close-inactive-issues.yml @@ -10,7 +10,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v8 + - uses: actions/stale@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 63b648c7b424..417681a63c55 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,7 +30,7 @@ jobs: - javascript steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: diff --git a/.github/workflows/combine-prs.yml b/.github/workflows/combine-prs.yml index 826472911980..ce746cf22f14 100644 --- a/.github/workflows/combine-prs.yml +++ b/.github/workflows/combine-prs.yml @@ -11,6 +11,6 @@ jobs: runs-on: ubuntu-latest steps: - name: combine-prs - uses: github/combine-prs@v3.1.1 + uses: github/combine-prs@v5.0.0 with: github_token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml index 8480a68a49c7..0c97ce9e0323 100644 --- a/.github/workflows/cross-version.yml +++ b/.github/workflows/cross-version.yml @@ -10,9 +10,7 @@ on: - '*' env: - ENTERPRISE_TESTDISTRIBUTION_ENABLED: true - BUILDCACHE_USERNAME: ${{ secrets.BUILD_CACHE_USERNAME }} - BUILDCACHE_PASSWORD: ${{ secrets.BUILD_CACHE_PASSWORD }} + DEVELOCITY_TESTDISTRIBUTION_ENABLED: true GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} jobs: @@ -20,12 +18,12 @@ jobs: strategy: fail-fast: false matrix: - jdk: [20, 21, 22] + jdk: [22] name: "OpenJDK ${{ matrix.jdk }}" runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Set up Test JDK diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index de5d0346a9f6..f0737751f838 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Validate Gradle wrapper diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml index 763dfe889565..b91cf84c462b 100644 --- a/.github/workflows/issue-labels.yml +++ b/.github/workflows/issue-labels.yml @@ -9,7 +9,7 @@ jobs: permissions: issues: write steps: - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 with: script: | github.rest.issues.addLabels({ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 676ec1a2f87a..82b17af92f9d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,9 +10,7 @@ on: - '*' env: - ENTERPRISE_TESTDISTRIBUTION_ENABLED: true - BUILDCACHE_USERNAME: ${{ secrets.BUILD_CACHE_USERNAME }} - BUILDCACHE_PASSWORD: ${{ secrets.BUILD_CACHE_PASSWORD }} + DEVELOCITY_TESTDISTRIBUTION_ENABLED: true GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }} jobs: @@ -20,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Install Graphviz @@ -31,12 +29,13 @@ jobs: uses: graalvm/setup-graalvm@v1 with: version: 'latest' - java-version: '17' + java-version: '21' components: 'native-image' github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build uses: ./.github/actions/main-build with: + dependency-graph: generate-and-submit arguments: | -Ptesting.enableJaCoCo build @@ -49,7 +48,7 @@ jobs: runs-on: windows-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Build @@ -59,7 +58,7 @@ jobs: runs-on: macos-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Build @@ -72,7 +71,7 @@ jobs: if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Publish @@ -93,18 +92,16 @@ jobs: if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && github.ref == 'refs/heads/main' steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Install Graphviz run: | sudo apt-get update sudo apt-get install graphviz - - name: Restore Gradle cache and display toolchains + - name: Upload Documentation uses: ./.github/actions/run-gradle with: - arguments: --quiet - - name: Upload Documentation + arguments: gitPublishPush -Dscan.tag.Documentation env: GRGIT_USER: ${{ secrets.GH_TOKEN }} - run: ./gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml index a32a51ac0002..724405a4c7d8 100644 --- a/.github/workflows/reproducible-build.yml +++ b/.github/workflows/reproducible-build.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Restore Gradle cache and display toolchains diff --git a/README.md b/README.md index 43f900ade772..5e0a293bffa7 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This repository is the home of _JUnit 5_. ## Latest Releases -- General Availability (GA): [JUnit 5.10.0](https://github.com/junit-team/junit5/releases/tag/r5.10.0) (July 23, 2023) +- General Availability (GA): [JUnit 5.10.1](https://github.com/junit-team/junit5/releases/tag/r5.10.1) (November 5, 2023) - Preview (Milestone/Release Candidate): N/A ## Documentation @@ -46,11 +46,14 @@ A code coverage report can also be generated locally via the [Gradle Wrapper] by executing `./gradlew -Ptesting.enableJaCoCo clean jacocoRootReport`. The results will be available in `build/reports/jacoco/jacocoRootReport/html/index.html`. -## Gradle Enterprise +## Develocity -[![Revved up by Gradle Enterprise](https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) +[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.junit.org/scans) -JUnit 5 utilizes [Gradle Enterprise](https://gradle.com/) for _Build Scans_, _Build Cache_, and _Test Distribution_. +JUnit 5 utilizes [Develocity](https://gradle.com/) for [Build Scans](https://scans.gradle.com/), +[Build Cache](https://docs.gradle.org/current/userguide/build_cache.html), +[Predictive Test Selection](https://docs.gradle.com/enterprise/predictive-test-selection/), and +[Test Distribution](https://docs.gradle.com/enterprise/test-distribution/). The latest Build Scans are available on [ge.junit.org](https://ge.junit.org/). Currently, only core team members can publish Build Scans and use Test Distribution on that server. @@ -62,7 +65,7 @@ task outputs from previous CI builds. ## Building from Source -You need [JDK 17] to build JUnit 5. [Gradle toolchains] are used to detect and +You need [JDK 21] to build JUnit 5. [Gradle toolchains] are used to detect and potentially download additional JDKs for compilation and test execution. All modules can be _built_ and _tested_ with the [Gradle Wrapper] using the following command. @@ -97,7 +100,7 @@ See also for releases and [Gradle Wrapper]: https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper [JaCoCo]: https://www.eclemma.org/jacoco/ [Javadoc]: https://junit.org/junit5/docs/current/api/ -[JDK 17]: https://foojay.io/almanac/java-17/ +[JDK 21]: https://javaalmanac.io/jdk/21/ [Release Notes]: https://junit.org/junit5/docs/current/release-notes/ [Samples]: https://github.com/junit-team/junit5-samples [StackOverflow]: https://stackoverflow.com/questions/tagged/junit5 diff --git a/SECURITY.md b/SECURITY.md index fca52da512fa..cb9359153b13 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,10 +2,10 @@ ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| 5.9.x | :white_check_mark: | -| < 5.9 | :x: | +| Version | Supported | +| -------- | ------------------ | +| 5.10.x | :white_check_mark: | +| < 5.10 | :x: | ## Reporting a Vulnerability diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index fd4877cdd6fc..ad16a4294821 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -11,6 +11,7 @@ plugins { alias(libs.plugins.asciidoctorConvert) alias(libs.plugins.asciidoctorPdf) alias(libs.plugins.gitPublish) + alias(libs.plugins.plantuml) id("junitbuild.build-parameters") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") @@ -63,8 +64,7 @@ dependencies { asciidoctorj { modules { - diagram.use() - pdf.version(libs.versions.asciidoctor.pdf) + pdf.version(libs.versions.asciidoctorj.pdf) } requires(file("src/docs/asciidoc/resources/themes/rouge_junit.rb")) } @@ -207,6 +207,13 @@ tasks { outputFile = standaloneConsoleLauncherShadowedArtifactsFile } + plantUml { + fileFormat = "SVG" + outputs.cacheIf { true } + } + + val componentDiagram = plantUml.flatMap { it.outputDirectory.file("component-diagram.svg") } + withType().configureEach { inputs.files( generateConsoleLauncherOptions, @@ -215,7 +222,8 @@ tasks { generateConsoleLauncherEnginesOptions, generateExperimentalApisTable, generateDeprecatedApisTable, - generateStandaloneConsoleLauncherShadowedArtifactsFile + generateStandaloneConsoleLauncherShadowedArtifactsFile, + componentDiagram ) resources { @@ -223,6 +231,9 @@ tasks { include("**/images/**/*.png") include("**/images/**/*.svg") } + from(componentDiagram) { + into("user-guide/images") + } } // Temporary workaround for https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/599 diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index 6aa27c3d46b8..712c8709a63f 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -14,6 +14,7 @@ endif::[] :ClassSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ClassSupport.html[ClassSupport] :ModifierSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ModifierSupport.html[ModifierSupport] :ReflectionSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/ReflectionSupport.html[ReflectionSupport] +:StringConversionSupport: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/support/conversion/StringConversionSupport.html[StringConversionSupport] :Testable: {javadoc-root}/org.junit.platform.commons/org/junit/platform/commons/annotation/Testable.html[@Testable] // Platform Console Launcher :junit-platform-console: {javadoc-root}/org.junit.platform.console/org/junit/platform/console/package-summary.html[junit-platform-console] diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index 800b74226ff8..bc63c1a43fcf 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -6,6 +6,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juli :docinfodir: {includedir}/docinfos :docinfo2: :numbered!: +:last-update-label!: // This document contains the _change log_ for all JUnit 5 releases since 5.10 GA. @@ -18,4 +19,6 @@ include::{includedir}/link-attributes.adoc[] include::{basedir}/release-notes-5.11.0-M1.adoc[] +include::{basedir}/release-notes-5.10.1.adoc[] + include::{basedir}/release-notes-5.10.0.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc new file mode 100644 index 000000000000..af0ac43916b4 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc @@ -0,0 +1,60 @@ +[[release-notes-5.10.1]] +== 5.10.1 + +*Date of Release:* November 5, 2023 + +*Scope:* minor bug fixes and improvements since 5.10.0. + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/72?closed=1+[5.10.1] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-5.10.1-junit-platform]] +=== JUnit Platform + +==== Bug Fixes + +* Field predicates are now applied while searching the type hierarchy. This fixes bugs in + `findFields(...)` and `streamFields(...)` in `ReflectionSupport` as well as + `findAnnotatedFields(...)` and `findAnnotatedFieldValues(...)` in `AnnotationSupport`. + - See link:https://github.com/junit-team/junit5/issues/3532[issue 3532] for details. +* Method predicates are now applied while searching the type hierarchy. This fixes bugs + in `findMethods(...)` and `streamMethods(...)` in `ReflectionSupport` as well as + `findAnnotatedMethods(...)` in `AnnotationSupport`. + - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. + + +[[release-notes-5.10.1-junit-jupiter]] +=== JUnit Jupiter + +==== Bug Fixes + +* A package-private static field annotated with `@TempDir` is no longer _shadowed_ by a + non-static field annotated with `@TempDir` when the non-static field resides in a + different package and has the same name as the static field. + - See link:https://github.com/junit-team/junit5/issues/3532[issue 3532] for details. +* A package-private class-level lifecycle method annotated with `@BeforeAll` or + `@AfterAll` is no longer _shadowed_ by a method-level lifecycle method annotated with + `@BeforeEach` or `@AfterEach` when the method-level lifecycle method resides in a + different package and has the same name as the class-level lifecycle method. + - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. +* The `ON_SUCCESS` cleanup mode of `@TempDir` now takes into account failures of test + methods and nested tests when it's declared on the class level, e.g. as a static field. +* The `RandomNumberExtension` example in the + <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been + updated to properly support `Integer` types as well as non-static field injection. + +==== New Features and Improvements + +* Improved Javadoc for `Assertions.assertTimeoutPreemptively` regarding thread interrupt. +* Documentation for `@Disabled` and conditional annotations now explicitly explains that + such annotations are not inherited by subclasses. + + +[[release-notes-5.10.1-junit-vintage]] +=== JUnit Vintage + +==== Bug Fixes + +* Fixed reporting for JUnit 3 test classes that use JUnit 4's `@Ignored` annotation. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc index b071e738f8d7..f6ce1a5caad4 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc @@ -6,8 +6,8 @@ *Scope:* ❓ For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/68?closed=1+[5.11.0-M1] milestone page in the -JUnit repository on GitHub. +link:{junit5-repo}+/milestone/68?closed=1+[5.11.0-M1] milestone page in the JUnit +repository on GitHub. [[release-notes-5.11.0-M1-junit-platform]] @@ -15,7 +15,9 @@ JUnit repository on GitHub. ==== Bug Fixes -* ❓ +* Allow `junit-platform-launcher` to be used as a Java module when + `junit.platform.launcher.interceptors.enabled` is set to `true`. + - See link:https://github.com/junit-team/junit5/issues/3561[issue 3561] for details. ==== Deprecations and Breaking Changes @@ -23,7 +25,12 @@ JUnit repository on GitHub. ==== New Features and Improvements -* Introduced kotlin contracts for kotlin assertion methods +* New `StringConversionSupport` in `junit-platform-commons` to expose internal conversion + logic used by Jupiter's `DefaultArgumentConverter` for use in third-party extensions and + test engines. +* Error messages for type mismatches in `NamespacedHierarchicalStore` now include the + actual type and value in addition to the required type. +* Introduced kotlin contracts for kotlin assertion methods. [[release-notes-5.11.0-M1-junit-jupiter]] === JUnit Jupiter @@ -34,7 +41,7 @@ JUnit repository on GitHub. ==== Deprecations and Breaking Changes -* Change used Kotlin API and language version from 1.3 to 1.6 +* Change used Kotlin API and language version from 1.3 to 1.6. ==== New Features and Improvements diff --git a/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/documentation/src/docs/asciidoc/user-guide/appendix.adoc index a5d8290b5d75..7582ba606f0b 100644 --- a/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ b/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -133,100 +133,4 @@ following _OpenTest4J_ JAR. [[dependency-diagram]] === Dependency Diagram -[plantuml, component-diagram, svg] ----- -skinparam { - defaultFontName Open Sans -} - -package org.junit.jupiter { - [junit-jupiter] as jupiter - [junit-jupiter-api] as jupiter_api - [junit-jupiter-engine] as jupiter_engine - [junit-jupiter-params] as jupiter_params - [junit-jupiter-migrationsupport] as jupiter_migration_support -} - -package org.junit.vintage { - [junit-vintage-engine] as vintage_engine -} - -package org.junit.platform { - [junit-platform-commons] as commons - [junit-platform-console] as console - [junit-platform-engine] as engine - [junit-platform-jfr] as jfr - [junit-platform-launcher] as launcher - [junit-platform-reporting] as reporting - [junit-platform-runner] as runner - [junit-platform-suite] as suite - [junit-platform-suite-api] as suite_api - [junit-platform-suite-commons] as suite_commons - [junit-platform-suite-engine] as suite_engine - [junit-platform-testkit] as testkit -} - -package "JUnit 4" { - [junit:junit] as junit4 -} - -package org.opentest4j { - [opentest4j] -} - -package org.apiguardian { - [apiguardian-api] as apiguardian - note bottom of apiguardian #white - All artifacts except - opentest4j and junit:junit - have a dependency on this - artifact. The edges have - been omitted from this - diagram for the sake of - readability. - endnote -} - -jupiter ..> jupiter_api -jupiter ..> jupiter_params -jupiter ..> jupiter_engine - -jupiter_api ....> opentest4j -jupiter_api ...> commons - -jupiter_engine ...> engine -jupiter_engine ..> jupiter_api - -jupiter_params ..> jupiter_api -jupiter_migration_support ..> jupiter_api -jupiter_migration_support ...> junit4 - -console ..> launcher -console ..> reporting - -launcher ..> engine - -jfr ..> launcher - -engine ....> opentest4j -engine ..> commons - -reporting ..> launcher - -runner ..> suite_commons -runner ...> junit4 - -suite ..> suite_api -suite ..> suite_engine - -suite_engine ..> suite_commons - -suite_commons ..> launcher -suite_commons ..> suite_api - -testkit ....> opentest4j -testkit ..> launcher - -vintage_engine ...> engine -vintage_engine ..> junit4 ----- +image::component-diagram.svg[] diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index 09ca2ec5e88b..48ded08a70d5 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -99,7 +99,7 @@ public @interface DatabaseAndWebServerExtension { The above examples demonstrate how `@ExtendWith` can be applied at the class level or at the method level; however, for certain use cases it makes sense for an extension to be registered declaratively at the field or parameter level. Consider a -`RandomNumberExtension` that generates random numbers that can be injected into a field or +`RandomNumberExtension` which generates random numbers that can be injected into a field or via a parameter in a constructor, test method, or lifecycle method. If the extension provides a `@Random` annotation that is meta-annotated with `@ExtendWith(RandomNumberExtension.class)` (see listing below), the extension can be used @@ -118,17 +118,29 @@ include::{testDir}/example/extensions/RandomNumberDemo.java[tags=user_guide] [[extensions-RandomNumberExtension]] The following code listing provides an example of how one might choose to implement such a `RandomNumberExtension`. This implementation works for the use cases in -`RandomNumberDemo`; however, it may not prove robust enough to cover all use cases – for -example, the random number generation support is limited to integers, it uses -`java.util.Random` instead of `java.security.SecureRandom`, etc. In any case, it is +`RandomNumberDemo`; however, it may not prove robust enough to cover all use cases -- for +example, the random number generation support is limited to integers; it uses +`java.util.Random` instead of `java.security.SecureRandom`; etc. In any case, it is important to note which extension APIs are implemented and for what reasons. Specifically, `RandomNumberExtension` implements the following extension APIs: - `BeforeAllCallback`: to support static field injection -- `TestInstancePostProcessor`: to support non-static field injection +- `BeforeEachCallback`: to support non-static field injection - `ParameterResolver`: to support constructor and method injection +[NOTE] +==== +Ideally, the `RandomNumberExtension` would implement `TestInstancePostProcessor` instead +of `BeforeEachCallback` in order to support non-static field injection immediately after +the test class has been instantiated. + +However, JUnit Jupiter currently does not allow a `TestInstancePostProcessor` to be +registered via `@ExtendWith` on a non-static field (see +link:{junit5-repo}/issues/3437[issue 3437]). In light of that, the `RandomNumberExtension` +implements `BeforeEachCallback` as an alternative approach. +==== + [source,java,indent=0] ---- include::{testDir}/example/extensions/RandomNumberExtension.java[tags=user_guide] @@ -675,11 +687,11 @@ method in the inverse order they were added in. [[extensions-supported-utilities]] === Supported Utilities in Extensions -The `junit-platform-commons` artifact exposes a package named -`{junit-platform-support-package}` that contains _maintained_ utility methods for working -with annotations, classes, reflection, and classpath scanning tasks. `TestEngine` and -`Extension` authors are encouraged to use these supported methods in order to align with -the behavior of the JUnit Platform. +The `junit-platform-commons` artifact provides _maintained_ utilities for working with +annotations, classes, reflection, classpath scanning, and conversion tasks. These +utilities can be found in the `{junit-platform-support-package}` and its subpackages. +`TestEngine` and `Extension` authors are encouraged to use these supported utilities in +order to align with the behavior of the JUnit Platform and JUnit Jupiter. [[extensions-supported-utilities-annotations]] ==== Annotation Support @@ -716,6 +728,16 @@ modifiers -- for example, to determine if a member is declared as `public`, `pri `abstract`, `static`, etc. Consult the Javadoc for `{ModifierSupport}` for further details. +[[extensions-supported-utilities-conversion]] +==== Conversion Support + +`StringConversionSupport` (in the `org.junit.platform.commons.support.conversion` package) +provides support for converting from strings to primitive types and their corresponding +wrapper types, date and time types from the `java.time package`, and some additional +common Java types such as `File`, `BigDecimal`, `BigInteger`, `Currency`, `Locale`, `URI`, +`URL`, `UUID`, etc. Consult the Javadoc for `{StringConversionSupport}` for further +details. + [[extensions-execution-order]] === Relative Execution Order of User Code and Extensions diff --git a/documentation/src/docs/asciidoc/user-guide/index.adoc b/documentation/src/docs/asciidoc/user-guide/index.adoc index 57e0f30948e0..9c34f8d33739 100644 --- a/documentation/src/docs/asciidoc/user-guide/index.adoc +++ b/documentation/src/docs/asciidoc/user-guide/index.adoc @@ -16,6 +16,7 @@ ifdef::backend-pdf[:imagesdir: {imagesoutdir}] // :sectnums: :toclevels: 4 +:last-update-label!: // include::{includedir}/link-attributes.adoc[] diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 0a0862b6f2b8..4f8d59925fbf 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -371,12 +371,22 @@ And here's a test class that contains a `@Disabled` test method. include::{testDir}/example/DisabledTestsDemo.java[tags=user_guide] ---- -NOTE: `@Disabled` may be declared without providing a _reason_; however, the JUnit team +[TIP] +==== +`@Disabled` may be declared without providing a _reason_; however, the JUnit team recommends that developers provide a short explanation for why a test class or test method has been disabled. Consequently, the above examples both show the use of a reason -- for example, `@Disabled("Disabled until bug #42 has been resolved")`. Some development teams even require the presence of issue tracking numbers in the _reason_ for automated traceability, etc. +==== + +[NOTE] +==== +`@Disabled` is not `@Inherited`. Consequently, if you wish to disable a class whose +superclass is `@Disabled`, you must redeclare `@Disabled` on the subclass. +==== + [[writing-tests-conditional-execution]] === Conditional Test Execution @@ -406,6 +416,13 @@ example, the `@TestOnMac` annotation in the combine `@Test` and `@EnabledOnOs` in a single, reusable annotation. ==== +[NOTE] +==== +_Conditional_ annotations in JUnit Jupiter are not `@Inherited`. Consequently, if you wish +to apply the same semantics to subclasses, each conditional annotation must be redeclared +on each subclass. +==== + [WARNING] ==== Unless otherwise stated, each of the _conditional_ annotations listed in the following diff --git a/documentation/src/plantuml/component-diagram.puml b/documentation/src/plantuml/component-diagram.puml new file mode 100644 index 000000000000..4874f5e1abb8 --- /dev/null +++ b/documentation/src/plantuml/component-diagram.puml @@ -0,0 +1,98 @@ +@startuml + +skinparam { + defaultFontName sans-serif +} + +package org.junit.jupiter { + [junit-jupiter] as jupiter + [junit-jupiter-api] as jupiter_api + [junit-jupiter-engine] as jupiter_engine + [junit-jupiter-params] as jupiter_params + [junit-jupiter-migrationsupport] as jupiter_migration_support +} + +package org.junit.vintage { + [junit-vintage-engine] as vintage_engine +} + +package org.junit.platform { + [junit-platform-commons] as commons + [junit-platform-console] as console + [junit-platform-engine] as engine + [junit-platform-jfr] as jfr + [junit-platform-launcher] as launcher + [junit-platform-reporting] as reporting + [junit-platform-runner] as runner + [junit-platform-suite] as suite + [junit-platform-suite-api] as suite_api + [junit-platform-suite-commons] as suite_commons + [junit-platform-suite-engine] as suite_engine + [junit-platform-testkit] as testkit +} + +package "JUnit 4" { + [junit:junit] as junit4 +} + +package org.opentest4j { + [opentest4j] +} + +package org.apiguardian { + [apiguardian-api] as apiguardian + note bottom of apiguardian #white + All artifacts except + opentest4j and junit:junit + have a dependency on this + artifact. The edges have + been omitted from this + diagram for the sake of + readability. + endnote +} + +jupiter ..> jupiter_api +jupiter ..> jupiter_params +jupiter ..> jupiter_engine + +jupiter_api ....> opentest4j +jupiter_api ...> commons + +jupiter_engine ...> engine +jupiter_engine ..> jupiter_api + +jupiter_params ..> jupiter_api +jupiter_migration_support ..> jupiter_api +jupiter_migration_support ...> junit4 + +console ..> launcher +console ..> reporting + +launcher ..> engine + +jfr ..> launcher + +engine ....> opentest4j +engine ..> commons + +reporting ..> launcher + +runner ..> suite_commons +runner ...> junit4 + +suite ..> suite_api +suite ..> suite_engine + +suite_engine ..> suite_commons + +suite_commons ..> launcher +suite_commons ..> suite_api + +testkit ....> opentest4j +testkit ..> launcher + +vintage_engine ...> engine +vintage_engine ..> junit4 + +@enduml diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java index 8dd274bda73c..0589e1e19b29 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberDemo.java +++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -27,7 +27,7 @@ class RandomNumberDemo { private int randomNumber1; RandomNumberDemo(@Random int randomNumber2) { - // Use randomNumber2 in constructor + // Use randomNumber2 in constructor. } @BeforeEach diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java index 3bbece08e206..f550a57cd3d0 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberExtension.java +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -18,17 +18,17 @@ import java.util.function.Predicate; import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.platform.commons.support.ModifierSupport; // end::user_guide[] // @formatter:off // tag::user_guide[] class RandomNumberExtension - implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver { + implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { private final java.util.Random random = new java.util.Random(System.nanoTime()); @@ -47,8 +47,9 @@ public void beforeAll(ExtensionContext context) { * {@code @Random} and can be assigned an integer value. */ @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + public void beforeEach(ExtensionContext context) { Class testClass = context.getRequiredTestClass(); + Object testInstance = context.getRequiredTestInstance(); injectFields(testClass, testInstance, ModifierSupport::isNotStatic); } @@ -86,7 +87,7 @@ private void injectFields(Class testClass, Object testInstance, } private static boolean isInteger(Class type) { - return int.class.isAssignableFrom(type); + return type == Integer.class || type == int.class; } } diff --git a/gradle.properties b/gradle.properties index 9de53c4570a6..65cc27143d99 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryEr --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED org.gradle.caching=true org.gradle.parallel=true -org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21 +org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21,JDK22 org.gradle.kotlin.dsl.allWarningsAsErrors=true # Test Distribution diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e09185b07577..6169a750abe2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,22 +1,22 @@ [versions] -ant = "1.10.13" +ant = "1.10.14" apiguardian = "1.1.2" -asciidoctor-pdf = "1.5.3" +asciidoctorj-pdf = "2.3.9" asciidoctor-plugins = "4.0.0-alpha.1" # Check if workaround in documentation.gradle.kts can be removed when upgrading assertj = "3.24.2" -bnd = "6.4.0" -checkstyle = "10.12.1" -gradleVersionsPlugin = "0.47.0" +bnd = "7.0.0" +checkstyle = "10.12.5" +gradleVersionsPlugin = "0.50.0" jacoco = "0.8.7" -jmh = "1.36" +jmh = "1.37" junit4 = "4.13.2" junit4Osgi = "4.13.2_1" junit4Min = "4.12" ktlint = "0.48.2" -log4j = "2.20.0" +log4j = "2.22.0" opentest4j = "1.3.0" openTestReporting = "0.1.0-M1" -surefire = "3.1.2" +surefire = "3.2.2" xmlunit = "2.9.1" [libraries] @@ -24,21 +24,21 @@ ant = { module = "org.apache.ant:ant", version.ref = "ant" } ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } -archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.0.1" } +archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.2.1" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } 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.161" } -commons-io = { module = "commons-io:commons-io", version = "2.13.0" } -gradle-commonCustomUserData = { module = "com.gradle:common-custom-user-data-gradle-plugin", version = "1.11.1" } -gradle-foojayResolver = { module = "org.gradle.toolchains:foojay-resolver", version = "0.6.0" } -gradle-enterprise = { module = "com.gradle:gradle-enterprise-gradle-plugin", version = "3.14" } +classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.165" } +commons-io = { module = "commons-io:commons-io", version = "2.15.1" } +gradle-commonCustomUserData = { module = "com.gradle:common-custom-user-data-gradle-plugin", version = "1.12.1" } +gradle-foojayResolver = { module = "org.gradle.toolchains:foojay-resolver", version = "0.7.0" } +gradle-enterprise = { module = "com.gradle:gradle-enterprise-gradle-plugin", version = "3.16" } gradle-bnd = { module = "biz.aQute.bnd:biz.aQute.bnd.gradle", version.ref = "bnd" } gradle-shadow = { module = "com.github.johnrengelman:shadow", version = "8.1.1" } -gradle-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.20.0" } +gradle-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.23.3" } gradle-versions = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "gradleVersionsPlugin" } -groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.13" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.16" } groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.21" } hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } jfrunit = { module = "org.moditect.jfrunit:jfrunit-core", version = "1.0.0.Alpha2" } @@ -47,23 +47,26 @@ jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } joox = { module = "org.jooq:joox", version = "2.0.0" } junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.2" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.3" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } -maven = { module = "org.apache.maven:apache-maven", version = "3.9.3" } +maven = { module = "org.apache.maven:apache-maven", version = "3.9.6" } mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" } -memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.6.1" } -mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.4.0" } +memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.7.0" } +mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.8.0" } opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" } -picocli = { module = "info.picocli:picocli", version = "4.7.4" } -slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.7" } +picocli = { module = "info.picocli:picocli", version = "4.7.5" } +slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.9" } spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } -testingAnnotations = { module = "com.gradle:gradle-enterprise-testing-annotations", version = "1.1" } +testingAnnotations = { module = "com.gradle:develocity-testing-annotations", version = "2.0" } + +# Only declared here so Dependabot knows when to update the referenced versions +asciidoctorj-pdf = { module = "org.asciidoctor:asciidoctorj-pdf", version.ref = "asciidoctorj-pdf" } [bundles] ant = ["ant", "ant-junit", "ant-junitlauncher"] @@ -74,8 +77,9 @@ xmlunit = ["xmlunit-assertj", "xmlunit-placeholders"] asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciidoctor-plugins" } asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-plugins" } buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.3" } -gitPublish = { id = "org.ajoberstar.git-publish", version = "4.2.0" } -jmh = { id = "me.champeau.jmh", version = "0.7.1" } +gitPublish = { id = "org.ajoberstar.git-publish", version = "4.2.1" } +jmh = { id = "me.champeau.jmh", version = "0.7.2" } nohttp = { id = "io.spring.nohttp", version = "0.0.11" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0-rc-1" } +plantuml = { id = "io.freefair.plantuml", version = "8.4" } versions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersionsPlugin" } diff --git a/gradle/plugins/build-parameters/build.gradle.kts b/gradle/plugins/build-parameters/build.gradle.kts index f70f2d8bc062..25e16d2e5a61 100644 --- a/gradle/plugins/build-parameters/build.gradle.kts +++ b/gradle/plugins/build-parameters/build.gradle.kts @@ -15,17 +15,8 @@ buildParameters { description = "Defines the Java toolchain version to use for compiling code" } group("buildCache") { - string("username") { - description = "Username to authenticate with the remote build cache" - fromEnvironment() - } - string("password") { - description = "Password to authenticate with the remote build cache" - fromEnvironment() - } - string("url") { - description = "URL to the remote build cache" - fromEnvironment() + string("server") { + description = "Remote build cache server address (protocol and hostname), e.g. https://eu-build-cache-ge.junit.org" } } group("documentation") { @@ -35,7 +26,7 @@ buildParameters { defaultValue = false } } - group("enterprise") { + group("develocity") { description = "Parameters controlling Gradle Enterprise features" group("predictiveTestSelection") { bool("enabled") { @@ -72,4 +63,9 @@ buildParameters { description = "Configures the number of times failing test are retried" } } + group("publishing") { + bool("signArtifacts") { + description = "Sign artifacts before publishing them to Maven repos" + } + } } diff --git a/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt b/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt index 834374bf06c6..64a61da7fb7b 100644 --- a/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt +++ b/gradle/plugins/common/src/main/kotlin/JavaLibraryExtension.kt @@ -2,6 +2,6 @@ import org.gradle.api.JavaVersion open class JavaLibraryExtension { var mainJavaVersion: JavaVersion = JavaVersion.VERSION_1_8 - var testJavaVersion: JavaVersion = JavaVersion.VERSION_17 + var testJavaVersion: JavaVersion = JavaVersion.VERSION_21 var configureRelease: Boolean = true } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts index 6eb7eb308ec8..5a18f0cc4b3d 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts @@ -2,6 +2,8 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import junitbuild.java.ModuleCompileOptions import junitbuild.java.ModulePathArgumentProvider import junitbuild.java.PatchModuleArgumentProvider +import org.gradle.plugins.ide.eclipse.model.Classpath +import org.gradle.plugins.ide.eclipse.model.Library plugins { `java-library` @@ -36,6 +38,12 @@ eclipse { } } } + classpath.file.whenMerged { + this as Classpath + // Remove classpath entries for non-existent libraries added by various + // plugins, such as "junit-jupiter-api/build/classes/kotlin/testFixtures". + entries.removeIf { it is Library && !file(it.path).exists() } + } } java { diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts index 68735c0c9168..41f9376e8dbd 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts @@ -5,7 +5,7 @@ plugins { } project.pluginManager.withPlugin("java") { - val defaultLanguageVersion = JavaLanguageVersion.of(17) + val defaultLanguageVersion = JavaLanguageVersion.of(21) val javaLanguageVersion = buildParameters.javaToolchainVersion.map { JavaLanguageVersion.of(it) }.getOrElse(defaultLanguageVersion) val extension = the() diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts index 8f25bde2c0dd..7ae9944b015c 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts @@ -5,6 +5,10 @@ plugins { kotlin("jvm") } +tasks.named("kotlinSourcesJar") { + enabled = false +} + tasks.withType().configureEach { kotlinOptions { apiVersion = "1.6" diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts index bab950933c2a..ad877035e1f3 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.publishing-conventions.gradle.kts @@ -40,17 +40,16 @@ tasks.withType().configureEach { dependsOn(tasks.build) } +val signArtifacts = buildParameters.publishing.signArtifacts.getOrElse(!(isSnapshot || buildParameters.ci)) + signing { useGpgCmd() sign(publishing.publications) - isRequired = !(isSnapshot || buildParameters.ci) + isRequired = signArtifacts } tasks.withType().configureEach { - val isSnapshot = project.version.toString().contains("SNAPSHOT") - onlyIf { - !isSnapshot // Gradle Module Metadata currently does not support signing snapshots - } + enabled = signArtifacts } publishing { diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts index 086e1e6135af..e0edfb317eff 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.testing-conventions.gradle.kts @@ -22,9 +22,9 @@ tasks.withType().configureEach { maxRetries = buildParameters.testing.retries.orElse(if (buildParameters.ci) 2 else 0) } distribution { - enabled.convention(buildParameters.enterprise.testDistribution.enabled && (!buildParameters.ci || System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY").isNotBlank())) - maxLocalExecutors = buildParameters.enterprise.testDistribution.maxLocalExecutors - maxRemoteExecutors = buildParameters.enterprise.testDistribution.maxRemoteExecutors + enabled.convention(buildParameters.develocity.testDistribution.enabled && (!buildParameters.ci || System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY").isNotBlank())) + maxLocalExecutors = buildParameters.develocity.testDistribution.maxLocalExecutors + maxRemoteExecutors = buildParameters.develocity.testDistribution.maxRemoteExecutors if (buildParameters.ci) { when { OperatingSystem.current().isLinux -> requirements.add("os=linux") @@ -34,7 +34,7 @@ tasks.withType().configureEach { } } predictiveSelection { - enabled = buildParameters.enterprise.predictiveTestSelection.enabled + enabled = buildParameters.develocity.predictiveTestSelection.enabled // Ensure PTS works when publishing Build Scans to scans.gradle.com this as PredictiveTestSelectionExtensionInternal diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 0b948c46d94f..43bfc1bd7621 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,4 +1,4 @@ -val expectedJavaVersion = JavaVersion.VERSION_17 +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}." diff --git a/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh b/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh deleted file mode 100755 index ecd0482a2368..000000000000 --- a/gradle/scripts/publishDocumentationSnapshotOnlyIfNecessary.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash - -readonly checksum_directory='documentation/build/checksum' -readonly current="${checksum_directory}/current-checksum.txt" -readonly published="${checksum_directory}/published-checksum.txt" -readonly github_pages_url='https://raw.githubusercontent.com/junit-team/junit5/gh-pages/docs/snapshot/published-checksum.txt' - -# -# always generate current sums -# -echo "Generating checksum file ${current}..." -mkdir --parents "${checksum_directory}" -md5sum documentation/documentation.gradle.kts > "${current}" -md5sum $(find documentation/src -type f) >> "${current}" -# skip module junit-bom because it doesn't contain relevant documentation -md5sum $(find junit-jupiter -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-api -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-migrationsupport -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-jupiter-params -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-commons -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-console -wholename '**/src/main/*.java') >> "${current}" -# skip module junit-platform-console-standalone because it doesn't contain relevant documentation -md5sum $(find junit-platform-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-jfr -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-launcher -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-reporting -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-runner -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-api -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-commons -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-suite-engine -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-platform-testkit -wholename '**/src/main/*.java') >> "${current}" -md5sum $(find junit-vintage-engine -wholename '**/src/main/*.java') >> "${current}" -# skip module platform-tests because it doesn't contain relevant documentation -# skip module platform-tooling-support-tests because it doesn't contain relevant documentation -sort --output "${current}" "${current}" -echo -md5sum "${current}" - -# -# compare current with published sums -# -curl --silent --output "${published}" "${github_pages_url}" -md5sum "${published}" -if cmp --silent "${current}" "${published}" ; then - # - # no changes detected: we're done - # - echo - echo "Already published documentation with same source checksum." - echo -else - # - # update checksum file and trigger new documentation build and upload - # - echo - echo "Creating and publishing documentation..." - echo - cp --force "${current}" "${published}" - ./gradlew gitPublishPush -Porg.gradle.java.installations.auto-download=false -Dscan.tag.Documentation -fi diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b7..d64cd4917707 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b62cae5b21db..db8c3baafe34 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=222818637ce0a4cb82e322bf847ea49ac319aecdb363d81acabd9e81315d08f6 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip +distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a5321..1aa94a426907 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java index 52743ebef416..40f2e949a3d9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -40,7 +40,7 @@ * methods may optionally declare parameters to be resolved by * {@link org.junit.jupiter.api.extension.ParameterResolver ParameterResolvers}. * - *

Using {@code private} visibility for {@code @BeforeAll} methods is + *

Using {@code private} visibility for {@code @AfterAll} methods is * strongly discouraged and will be disallowed in a future release. * *

Inheritance and Execution Order

diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java index 4be1561689dc..670f1373fb8d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java @@ -59,22 +59,26 @@ *

Preemptive Timeouts

* *

The various {@code assertTimeoutPreemptively()} methods in this class - * execute the provided {@code executable} or {@code supplier} in a different - * thread than that of the calling code. This behavior can lead to undesirable - * side effects if the code that is executed within the {@code executable} or - * {@code supplier} relies on {@link ThreadLocal} storage. + * execute the provided callback ({@code executable} or {@code supplier}) in a + * different thread than that of the calling code. If the timeout is exceeded, + * an attempt will be made to preemptively abort execution of the callback by + * {@linkplain Thread#interrupt() interrupting} the callback's thread. If the + * callback's thread does not return when interrupted, the thread will continue + * to run in the background after the {@code assertTimeoutPreemptively()} method + * has returned. * - *

One common example of this is the transactional testing support in the Spring - * Framework. Specifically, Spring's testing support binds transaction state to - * the current thread (via a {@code ThreadLocal}) before a test method is invoked. - * Consequently, if an {@code executable} or {@code supplier} provided to - * {@code assertTimeoutPreemptively()} invokes Spring-managed components that - * participate in transactions, any actions taken by those components will not be - * rolled back with the test-managed transaction. On the contrary, such actions - * will be committed to the persistent store (e.g., relational database) even - * though the test-managed transaction is rolled back. - * - *

Similar side effects may be encountered with other frameworks that rely on + *

Furthermore, the behavior of {@code assertTimeoutPreemptively()} methods + * can lead to undesirable side effects if the code that is executed within the + * callback relies on {@link ThreadLocal} storage. One common example of this is + * the transactional testing support in the Spring Framework. Specifically, Spring's + * testing support binds transaction state to the current thread (via a + * {@code ThreadLocal}) before a test method is invoked. Consequently, if a + * callback provided to {@code assertTimeoutPreemptively()} invokes Spring-managed + * components that participate in transactions, any actions taken by those + * components will not be rolled back with the test-managed transaction. On the + * contrary, such actions will be committed to the persistent store (e.g., + * relational database) even though the test-managed transaction is rolled back. + * Similar side effects may be encountered with other frameworks that rely on * {@code ThreadLocal} storage. * *

Extensibility

@@ -3410,11 +3414,8 @@ public static T assertTimeout(Duration timeout, ThrowingSupplier supplier * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * - *

Note: the {@code executable} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code executable} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. + *

See the {@linkplain Assertions Preemptive Timeouts} section of the + * class-level Javadoc for further details. * * @see #assertTimeoutPreemptively(Duration, Executable, String) * @see #assertTimeoutPreemptively(Duration, Executable, Supplier) @@ -3431,11 +3432,8 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * - *

Note: the {@code executable} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code executable} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. + *

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

Fails with the supplied failure {@code message}. * @@ -3454,11 +3452,8 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * Assert that execution of the supplied {@code executable} * completes before the given {@code timeout} is exceeded. * - *

Note: the {@code executable} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code executable} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. + *

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

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -3481,13 +3476,10 @@ public static void assertTimeoutPreemptively(Duration timeout, Executable execut * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * - *

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

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

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. + *

If the assertion passes then the {@code supplier}'s result is returned. * * @see #assertTimeoutPreemptively(Duration, Executable) * @see #assertTimeoutPreemptively(Duration, Executable, String) @@ -3504,13 +3496,10 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * - *

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

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

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. + *

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

Fails with the supplied failure {@code message}. * @@ -3529,13 +3518,10 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * - *

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

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

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. + *

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

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. @@ -3556,18 +3542,15 @@ public static T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier * Assert that execution of the supplied {@code supplier} * completes before the given {@code timeout} is exceeded. * + *

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

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

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

Note: the {@code supplier} will be executed in a different thread than - * that of the calling code. Furthermore, execution of the {@code supplier} will - * be preemptively aborted if the timeout is exceeded. See the - * {@linkplain Assertions Preemptive Timeouts} section of the class-level - * Javadoc for a discussion of possible undesirable side effects. - * *

If necessary, the failure message will be retrieved lazily from the * supplied {@code messageSupplier}. * diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java index 9a7717053491..6394a84634c3 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java @@ -30,6 +30,10 @@ *

When applied at the class level, all test methods within that class * are automatically disabled as well. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

When applied at the method level, the presence of this annotation does not * prevent the test class from being instantiated. Rather, it prevents the * execution of the test method and method-level lifecycle callbacks such as diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java index a3b0762e0de5..1b175ed34d91 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java @@ -22,13 +22,17 @@ import org.junit.jupiter.api.extension.ExtendWith; /** - * {@code @DisabledForJreRange} is used to signal that the annotated test class or - * test method is only disabled for a specific range of Java Runtime + * {@code @DisabledForJreRange} is used to signal that the annotated test class + * or test method is disabled for a specific range of Java Runtime * Environment (JRE) versions from {@link #min} to {@link #max}. * *

When applied at the class level, all test methods within that class will * be disabled on the same specified JRE versions. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java index 06bdd569048a..1d3f119662da 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java @@ -23,12 +23,16 @@ /** * {@code @DisabledIf} is used to signal that the annotated test class or test - * method is disabled only if the provided - * {@linkplain #value() condition} evaluates to {@code true}. + * method is disabled if the provided {@linkplain #value() condition} + * evaluates to {@code true}. * *

When applied at the class level, all test methods within that class will * be disabled on the same condition. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java index 0cc90eec72a4..ea4f0a147a67 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java @@ -31,6 +31,10 @@ *

When declared at the class level, the result will apply to all test methods * within that class as well. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java index deef2a4a8fa0..6b473d684cff 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java @@ -28,6 +28,10 @@ * is completely optional since {@code @DisabledIfEnvironmentVariable} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * * @since 5.6 * @see DisabledIfEnvironmentVariable * @see java.lang.annotation.Repeatable diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java index ff6877bf1a06..eb351cb2ea14 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java @@ -28,6 +28,10 @@ * is completely optional since {@code @DisabledIfSystemProperty} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * * @since 5.6 * @see DisabledIfSystemProperty * @see java.lang.annotation.Repeatable diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java index ac7d0937d848..ce767e6afa95 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java @@ -31,6 +31,10 @@ *

When declared at the class level, the result will apply to all test methods * within that class as well. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java index a96399e799be..06ea015ebf88 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java @@ -22,12 +22,16 @@ /** * {@code @DisabledInNativeImage} is used to signal that the annotated test class - * or test method is only disabled when executing within a GraalVM native + * or test method is disabled when executing within a GraalVM native * image. * *

When applied at the class level, all test methods within that class will * be disabled within a native image. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java index 2aa7f012c947..5d66386056cd 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java @@ -23,12 +23,16 @@ /** * {@code @DisabledOnJre} is used to signal that the annotated test class or - * test method is disabled on one or more specified Java - * Runtime Environment (JRE) {@linkplain #value versions}. + * test method is disabled on one or more specified Java Runtime + * Environment (JRE) {@linkplain #value versions}. * *

When applied at the class level, all test methods within that class * will be disabled on the same specified JRE versions. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java index f067c2290083..3e843a545387 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java @@ -34,6 +34,10 @@ * will be disabled on the same specified operating systems, architectures, or * the specified combinations of both. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java index b1efb7c88e0d..ed1a74b06e09 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java @@ -29,6 +29,10 @@ *

When applied at the class level, all test methods within that class will * be enabled on the same specified JRE versions. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java index 5bd4b3df30e7..f28b1f8d2a1a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java @@ -23,12 +23,16 @@ /** * {@code @EnabledIf} is used to signal that the annotated test class or test - * method is enabled only if the provided - * {@linkplain #value() condition} evaluates to {@code true}. + * method is only enabled if the provided {@linkplain #value() condition} + * evaluates to {@code true}. * *

When applied at the class level, all test methods within that class will * be enabled on the same condition. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java index 44cf3bf5940f..11c48fdf369a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java @@ -31,6 +31,10 @@ *

When declared at the class level, the result will apply to all test methods * within that class as well. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java index 3589ed58b217..928c8582dd80 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java @@ -28,6 +28,10 @@ * is completely optional since {@code @EnabledIfEnvironmentVariable} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * * @since 5.6 * @see EnabledIfEnvironmentVariable * @see java.lang.annotation.Repeatable diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java index f33bdfae4790..2a3a32c6f6c5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java @@ -28,6 +28,10 @@ * is completely optional since {@code @EnabledIfSystemProperty} is a {@linkplain * java.lang.annotation.Repeatable repeatable} annotation. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * * @since 5.6 * @see EnabledIfSystemProperty * @see java.lang.annotation.Repeatable diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java index 26cbcc10743a..99587b7085d5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java @@ -31,6 +31,10 @@ *

When declared at the class level, the result will apply to all test methods * within that class as well. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java index 33641509e7a0..98504f74f653 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java @@ -28,6 +28,10 @@ *

When applied at the class level, all test methods within that class will * be enabled within a native image. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java index ab6e18a193e4..9b454a5744ba 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java @@ -29,6 +29,10 @@ *

When applied at the class level, all test methods within that class * will be enabled on the same specified JRE versions. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java index de284cd68654..838579e4251a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java @@ -34,6 +34,10 @@ * will be enabled on the same specified operating systems, architectures, or * the specified combinations of both. * + *

This annotation is not {@link java.lang.annotation.Inherited @Inherited}. + * Consequently, if you wish to apply the same semantics to a subclass, this + * annotation must be redeclared on the subclass. + * *

If a test method is disabled via this annotation, that does not prevent * the test class from being instantiated. Rather, it prevents the execution of * the test method and method-level lifecycle callbacks such as {@code @BeforeEach} diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java index ef33de69dda6..01619bf454d2 100644 --- a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java @@ -41,7 +41,7 @@ * @see LoggerFactory * @see LogRecordListener */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ ElementType.TYPE, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(TrackLogRecords.Extension.class) public @interface TrackLogRecords { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java index a31c9c02ad27..30f461189637 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java @@ -17,8 +17,6 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.logging.Logger; -import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.EngineExecutionContext; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; @@ -29,8 +27,6 @@ @API(status = INTERNAL, since = "5.0") public class JupiterEngineExecutionContext implements EngineExecutionContext { - private static final Logger logger = LoggerFactory.getLogger(JupiterEngineExecutionContext.class); - private final State state; // The following is not "cloneable" State. @@ -53,8 +49,7 @@ public void close() throws Exception { ((AutoCloseable) extensionContext).close(); } catch (Exception e) { - logger.error(e, () -> "Caught exception while closing extension context: " + extensionContext); - throw e; + throw new JUnitException("Failed to close extension context", e); } } } 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 fe76498bd4ae..7ac385561839 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 @@ -79,6 +79,8 @@ class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterR static final Namespace NAMESPACE = Namespace.create(TempDirectory.class); private static final String KEY = "temp.dir"; + private static final String FAILURE_TRACKER = "failure.tracker"; + private static final String CHILD_FAILED = "child.failed"; // for testing purposes static final String FILE_OPERATIONS_KEY = "file.operations"; @@ -96,6 +98,7 @@ public TempDirectory(JupiterConfiguration configuration) { */ @Override public void beforeAll(ExtensionContext context) { + installFailureTracker(context); injectStaticFields(context, context.getRequiredTestClass()); } @@ -106,10 +109,20 @@ public void beforeAll(ExtensionContext context) { */ @Override public void beforeEach(ExtensionContext context) { + installFailureTracker(context); context.getRequiredTestInstances().getAllInstances() // .forEach(instance -> injectInstanceFields(context, instance)); } + private static void installFailureTracker(ExtensionContext context) { + context.getStore(NAMESPACE).put(FAILURE_TRACKER, (CloseableResource) () -> context.getParent() // + .ifPresent(it -> { + if (selfOrChildFailed(context)) { + it.getStore(NAMESPACE).put(CHILD_FAILED, true); + } + })); + } + private void injectStaticFields(ExtensionContext context, Class testClass) { injectFields(context, null, testClass, ReflectionUtils::isStatic); } @@ -257,6 +270,11 @@ static CloseablePath createTempDir(TempDirFactory factory, CleanupMode cleanupMo } } + private static boolean selfOrChildFailed(ExtensionContext context) { + return context.getExecutionException().isPresent() // + || context.getStore(NAMESPACE).getOrDefault(CHILD_FAILED, Boolean.class, false); + } + static class CloseablePath implements CloseableResource { private static final Logger logger = LoggerFactory.getLogger(CloseablePath.class); @@ -281,8 +299,7 @@ Path get() { @Override public void close() throws IOException { try { - if (cleanupMode == NEVER - || (cleanupMode == ON_SUCCESS && extensionContext.getExecutionException().isPresent())) { + if (cleanupMode == NEVER || (cleanupMode == ON_SUCCESS && selfOrChildFailed(extensionContext))) { logger.info(() -> "Skipping cleanup of temp dir " + dir + " due to cleanup mode configuration."); return; } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java index f96e1b6341d2..bdb5ac2e82d8 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java @@ -15,6 +15,7 @@ import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.EventConditions.reportEntry; import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; @@ -57,7 +58,10 @@ void exceptionsDuringCloseAreReportedAsSuppressed() { test(), // finishedWithFailure( // message("Exception in test"), // - suppressed(0, message("Exception in onClose"))))); + suppressed(0, // + message("Failed to close extension context"), // + cause(message("Exception in onClose")) // + )))); } @ExtendWith(ThrowingOnCloseExtension.class) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java index f8ded8659782..e604a59525ff 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java @@ -29,11 +29,10 @@ * * @since 5.0 */ -@TrackLogRecords class MultipleTestableAnnotationsTests extends AbstractJupiterTestEngineTests { @Test - void testAndRepeatedTest(LogRecordListener listener) { + void testAndRepeatedTest(@TrackLogRecords LogRecordListener listener) { discoverTests(request().selectors(selectClass(TestCase.class)).build()); // @formatter:off diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java index 5a8679e4acd7..cab152e7fe0e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java @@ -47,9 +47,8 @@ void shouldGetDisplayNameFromDisplayNameAnnotation() { } @Test - @TrackLogRecords void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationWithBlankStringPresent( - LogRecordListener listener) { + @TrackLogRecords LogRecordListener listener) { String displayName = DisplayNameUtils.determineDisplayName(BlankDisplayNameTestCase.class, () -> "default-name"); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java index d6e619d886bd..f7dbaf4316d3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java @@ -17,15 +17,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.withSettings; -import java.util.logging.Level; -import java.util.logging.LogRecord; - import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.EngineExecutionListener; /** @@ -89,12 +84,11 @@ void canOverrideAttributeWhenContextIsExtended() { } @Test - @TrackLogRecords - void closeAttemptExceptionWillBeThrownDownTheCallStack(LogRecordListener logRecordListener) throws Exception { + void closeAttemptExceptionWillBeThrownDownTheCallStack() throws Exception { ExtensionContext failingExtensionContext = mock(ExtensionContext.class, withSettings().extraInterfaces(AutoCloseable.class)); - Exception expectedException = new Exception("test message"); - doThrow(expectedException).when(((AutoCloseable) failingExtensionContext)).close(); + Exception expectedCause = new Exception("test message"); + doThrow(expectedCause).when(((AutoCloseable) failingExtensionContext)).close(); JupiterEngineExecutionContext newContext = originalContext.extend() // .withExtensionContext(failingExtensionContext) // @@ -102,10 +96,9 @@ void closeAttemptExceptionWillBeThrownDownTheCallStack(LogRecordListener logReco Exception actualException = assertThrows(Exception.class, newContext::close); - assertSame(expectedException, actualException); - assertThat(logRecordListener.stream(JupiterEngineExecutionContext.class, Level.SEVERE)) // - .extracting(LogRecord::getMessage) // - .containsOnly("Caught exception while closing extension context: " + failingExtensionContext); + assertThat(actualException) // + .hasMessage("Failed to close extension context") // + .hasCauseReference(expectedCause); } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java index 5b8a208d0492..aad66de58391 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java @@ -138,8 +138,7 @@ void fieldsWithTestInstancePerClass() { } @Test - @TrackLogRecords - void multipleRegistrationsViaField(LogRecordListener listener) { + void multipleRegistrationsViaField(@TrackLogRecords LogRecordListener listener) { assertOneTestSucceeded(MultipleRegistrationsViaFieldTestCase.class); assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); } @@ -158,8 +157,7 @@ void duplicateRegistrationViaField() { } @Test - @TrackLogRecords - void registrationOrder(LogRecordListener listener) { + void registrationOrder(@TrackLogRecords LogRecordListener listener) { assertOneTestSucceeded(AllInOneWithTestInstancePerMethodTestCase.class); assertThat(getRegisteredLocalExtensions(listener))// .containsExactly(// diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java index 09026c6b1362..001fc49053f9 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java @@ -106,8 +106,7 @@ void orderAnnotationOnNestedTestClassesWithGlobalConfig() { } @Test - @TrackLogRecords - void orderAnnotationOnNestedTestClassesWithLocalConfig(LogRecordListener listener) { + void orderAnnotationOnNestedTestClassesWithLocalConfig(@TrackLogRecords LogRecordListener listener) { executeTests(ClassOrderer.class, selectClass(OuterWithLocalConfig.class))// .assertStatistics(stats -> stats.succeeded(callSequence.size())); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java index ed86fd2a2448..00c8a113a889 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java @@ -179,8 +179,7 @@ void defaultOrderer() { } @Test - @TrackLogRecords - void randomWithBogusSeedRepeatedly(LogRecordListener listener) { + void randomWithBogusSeedRepeatedly(@TrackLogRecords LogRecordListener listener) { var seed = "explode"; var expectedMessagePattern = Pattern.compile( "Failed to convert configuration parameter \\[" + Pattern.quote(Random.RANDOM_SEED_PROPERTY_NAME) @@ -209,8 +208,7 @@ void randomWithBogusSeedRepeatedly(LogRecordListener listener) { } @Test - @TrackLogRecords - void randomWithDifferentSeedConsecutively(LogRecordListener listener) { + void randomWithDifferentSeedConsecutively(@TrackLogRecords LogRecordListener listener) { Set uniqueSequences = new HashSet<>(); for (var i = 0; i < 10; i++) { @@ -240,8 +238,7 @@ void randomWithDifferentSeedConsecutively(LogRecordListener listener) { } @Test - @TrackLogRecords - void randomWithCustomSeed(LogRecordListener listener) { + void randomWithCustomSeed(@TrackLogRecords LogRecordListener listener) { var seed = "42"; var expectedMessage = "Using custom seed for configuration parameter [" + Random.RANDOM_SEED_PROPERTY_NAME + "] with value [" + seed + "]."; @@ -268,8 +265,7 @@ void randomWithCustomSeed(LogRecordListener listener) { } @Test - @TrackLogRecords - void misbehavingMethodOrdererThatAddsElements(LogRecordListener listener) { + void misbehavingMethodOrdererThatAddsElements(@TrackLogRecords LogRecordListener listener) { Class testClass = MisbehavingByAddingTestCase.class; executeTestsInParallel(testClass).assertStatistics(stats -> stats.succeeded(2)); @@ -283,8 +279,7 @@ void misbehavingMethodOrdererThatAddsElements(LogRecordListener listener) { } @Test - @TrackLogRecords - void misbehavingMethodOrdererThatRemovesElements(LogRecordListener listener) { + void misbehavingMethodOrdererThatRemovesElements(@TrackLogRecords LogRecordListener listener) { Class testClass = MisbehavingByRemovingTestCase.class; executeTestsInParallel(testClass).assertStatistics(stats -> stats.succeeded(3)); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java index ddaaa214f786..4e47ec390bb4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -23,8 +24,11 @@ import java.nio.file.Path; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; @@ -141,13 +145,49 @@ void cleanupModeOnSuccessFailingField() { assertThat(onSuccessFailingFieldDir).exists(); } + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for static fields when tests are failing. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeOnSuccessFailingStaticField() { + LauncherDiscoveryRequest request = request()// + .selectors(selectClass(OnSuccessFailingStaticFieldCase.class))// + .build(); + executeTests(request); + + assertThat(onSuccessFailingFieldDir).exists(); + } + + /** + * Ensure that ON_SUCCESS cleanup modes are obeyed for static fields when nested tests are failing. + *

+ * Expect the TempDir not to be cleaned up. + */ + @Test + void cleanupModeOnSuccessFailingStaticFieldWithNesting() { + LauncherDiscoveryRequest request = request()// + .selectors(selectClass(OnSuccessFailingStaticFieldWithNestingCase.class))// + .build(); + executeTests(request); + + assertThat(onSuccessFailingFieldDir).exists(); + } + @AfterAll static void afterAll() throws IOException { - deleteIfExists(defaultFieldDir); - deleteIfExists(neverFieldDir); - deleteIfExists(alwaysFieldDir); - deleteIfExists(onSuccessFailingFieldDir); - deleteIfExists(onSuccessPassingFieldDir); + deleteIfNotNullAndExists(defaultFieldDir); + deleteIfNotNullAndExists(neverFieldDir); + deleteIfNotNullAndExists(alwaysFieldDir); + deleteIfNotNullAndExists(onSuccessFailingFieldDir); + deleteIfNotNullAndExists(onSuccessPassingFieldDir); + } + + static void deleteIfNotNullAndExists(Path dir) throws IOException { + if (dir != null) { + deleteIfExists(dir); + } } // ------------------------------------------------------------------- @@ -208,6 +248,41 @@ void testOnSuccessFailingField() { } } + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + static class OnSuccessFailingStaticFieldCase { + + @TempDir(cleanup = ON_SUCCESS) + static Path onSuccessFailingFieldDir; + + @Test + @Order(1) + void failing() { + TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; + fail(); + } + + @Test + @Order(2) + void passing() { + } + } + + static class OnSuccessFailingStaticFieldWithNestingCase { + + @TempDir(cleanup = ON_SUCCESS) + static Path onSuccessFailingFieldDir; + + @Nested + class NestedTestCase { + + @Test + void test() { + TempDirFieldTests.onSuccessFailingFieldDir = onSuccessFailingFieldDir; + fail(); + } + } + } + } @Nested @@ -314,11 +389,11 @@ void cleanupModeOnSuccessFailingParameter() { @AfterAll static void afterAll() throws IOException { - deleteIfExists(defaultParameterDir); - deleteIfExists(neverParameterDir); - deleteIfExists(alwaysParameterDir); - deleteIfExists(onSuccessFailingParameterDir); - deleteIfExists(onSuccessPassingParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(defaultParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(neverParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(alwaysParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(onSuccessFailingParameterDir); + TempDirFieldTests.deleteIfNotNullAndExists(onSuccessPassingParameterDir); } // ------------------------------------------------------------------- diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java index f0575de592c0..458e433a1f8b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java @@ -230,11 +230,14 @@ private void onlyAttemptsToDeleteUndeletablePathOnce(Class Path.of(it.getKeyValuePairs().get(UndeletableTestCase.TEMP_DIR))).findAny().orElseThrow(); assertSingleFailedTest(results, // - instanceOf(IOException.class), // - message("Failed to delete temp directory " + tempDir.toAbsolutePath() + ". " + // - "The following paths could not be deleted (see suppressed exceptions for details): , undeletable"), // - suppressed(0, instanceOf(DirectoryNotEmptyException.class)), // - suppressed(1, instanceOf(IOException.class), message("Simulated failure"))); + cause( // + instanceOf(IOException.class), // + message("Failed to delete temp directory " + tempDir.toAbsolutePath() + ". " + // + "The following paths could not be deleted (see suppressed exceptions for details): , undeletable"), // + suppressed(0, instanceOf(DirectoryNotEmptyException.class)), // + suppressed(1, instanceOf(IOException.class), message("Simulated failure")) // + ) // + ); } @Nested diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java index 6ada22bf2527..bf4e1e2250a0 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java @@ -107,8 +107,7 @@ void testWatcherIsNotInvokedForTestFactoryMethods() { } @Test - @TrackLogRecords - void testWatcherExceptionsAreLoggedAndSwallowed(LogRecordListener logRecordListener) { + void testWatcherExceptionsAreLoggedAndSwallowed(@TrackLogRecords LogRecordListener logRecordListener) { assertCommonStatistics(executeTestsForClass(ExceptionThrowingTestWatcherTestCase.class)); // @formatter:off diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java index 4886f2718607..996592f951cb 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java @@ -124,8 +124,7 @@ void specificTimeoutsAreUsedIfSet() { } @Test - @TrackLogRecords - void logsInvalidValues(LogRecordListener logRecordListener) { + void logsInvalidValues(@TrackLogRecords LogRecordListener logRecordListener) { when(extensionContext.getConfigurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME)).thenReturn( Optional.of("invalid")); @@ -143,8 +142,7 @@ void specificThreadModeIsUsed() { } @Test - @TrackLogRecords - void logsInvalidThreadModeValueAndReturnEmpty(LogRecordListener logRecordListener) { + void logsInvalidThreadModeValueAndReturnEmpty(@TrackLogRecords LogRecordListener logRecordListener) { when(extensionContext.getConfigurationParameter(DEFAULT_TIMEOUT_THREAD_MODE_PROPERTY_NAME)).thenReturn( Optional.of("invalid")); diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java index 4464c67c3363..221db53a6132 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java @@ -28,7 +28,7 @@ * *

Such methods must not be {@code private} or {@code static}. * - *

Argument Providers and Sources

+ *

Arguments Providers and Sources

* *

{@code @ParameterizedTest} methods must specify at least one * {@link org.junit.jupiter.params.provider.ArgumentsProvider ArgumentsProvider} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java index fe697c5696e3..adbb606da691 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java @@ -10,10 +10,7 @@ package org.junit.jupiter.params.converter; -import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableList; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; import java.io.File; import java.math.BigDecimal; @@ -21,13 +18,13 @@ import java.net.URI; import java.net.URL; import java.util.Currency; -import java.util.List; import java.util.Locale; -import java.util.Optional; import java.util.UUID; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.platform.commons.support.conversion.ConversionException; +import org.junit.platform.commons.support.conversion.StringConversionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.ReflectionUtils; @@ -47,23 +44,13 @@ * * @since 5.0 * @see org.junit.jupiter.params.converter.ArgumentConverter + * @see org.junit.platform.commons.support.conversion.StringConversionSupport */ @API(status = INTERNAL, since = "5.0") public class DefaultArgumentConverter implements ArgumentConverter { public static final DefaultArgumentConverter INSTANCE = new DefaultArgumentConverter(); - private static final List stringToObjectConverters = unmodifiableList(asList( // - new StringToBooleanConverter(), // - new StringToCharacterConverter(), // - new StringToNumberConverter(), // - new StringToClassConverter(), // - new StringToEnumConverter(), // - new StringToJavaTimeConverter(), // - new StringToCommonJavaTypesConverter(), // - new FallbackStringToObjectConverter() // - )); - private DefaultArgumentConverter() { // nothing to initialize } @@ -88,34 +75,19 @@ public final Object convert(Object source, Class targetType, ParameterContext } if (source instanceof String) { - Class targetTypeToUse = toWrapperType(targetType); - Optional converter = stringToObjectConverters.stream().filter( - candidate -> candidate.canConvert(targetTypeToUse)).findFirst(); - if (converter.isPresent()) { - Class declaringClass = context.getDeclaringExecutable().getDeclaringClass(); - ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass); - try { - return converter.get().convert((String) source, targetTypeToUse, classLoader); - } - catch (Exception ex) { - if (ex instanceof ArgumentConversionException) { - // simply rethrow it - throw (ArgumentConversionException) ex; - } - // else - throw new ArgumentConversionException( - "Failed to convert String \"" + source + "\" to type " + targetType.getTypeName(), ex); - } + Class declaringClass = context.getDeclaringExecutable().getDeclaringClass(); + ClassLoader classLoader = ClassLoaderUtils.getClassLoader(declaringClass); + try { + return StringConversionSupport.convert((String) source, targetType, classLoader); + } + catch (ConversionException ex) { + throw new ArgumentConversionException(ex.getMessage(), ex); } } + throw new ArgumentConversionException( String.format("No built-in converter for source type %s and target type %s", source.getClass().getTypeName(), targetType.getTypeName())); } - private static Class toWrapperType(Class targetType) { - Class wrapperType = getWrapperType(targetType); - return wrapperType != null ? wrapperType : targetType; - } - } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java index 04f7849d91e0..aa2eb1ced01a 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java @@ -23,7 +23,7 @@ /** * {@code @ArgumentsSource} is a {@linkplain Repeatable repeatable} annotation - * that is used to register {@linkplain ArgumentsProvider argument providers} + * that is used to register {@linkplain ArgumentsProvider arguments providers} * for the annotated test method. * *

{@code @ArgumentsSource} may also be used as a meta-annotation in order to diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 2f248bfdad42..7ccc5250604b 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -111,6 +111,40 @@ */ class ParameterizedTestIntegrationTests { + @ParameterizedTest + @CsvSource(textBlock = """ + apple, True + banana, true + lemon, false + kumquat, FALSE + """) + void sweetFruit(String fruit, Boolean sweet) { + switch (fruit) { + case "apple" -> assertThat(sweet).isTrue(); + case "banana" -> assertThat(sweet).isTrue(); + case "lemon" -> assertThat(sweet).isFalse(); + case "kumquat" -> assertThat(sweet).isFalse(); + default -> fail("Unexpected fruit : " + fruit); + } + } + + @ParameterizedTest + @CsvSource(nullValues = "null", textBlock = """ + apple, True + banana, true + lemon, false + kumquat, null + """) + void sweetFruitWithNullableBoolean(String fruit, Boolean sweet) { + switch (fruit) { + case "apple" -> assertThat(sweet).isTrue(); + case "banana" -> assertThat(sweet).isTrue(); + case "lemon" -> assertThat(sweet).isFalse(); + case "kumquat" -> assertThat(sweet).isNull(); // null --> null + default -> fail("Unexpected fruit : " + fruit); + } + } + @ParameterizedTest @CsvSource(quoteCharacter = '"', textBlock = """ diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java index 90d8fb062792..b2b1e7667722 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java @@ -47,6 +47,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils; @@ -61,11 +63,13 @@ class DefaultArgumentConverterTests { void isAwareOfNull() { assertConverts(null, Object.class, null); assertConverts(null, String.class, null); + assertConverts(null, Boolean.class, null); } @Test void isAwareOfWrapperTypesForPrimitiveTypes() { assertConverts(true, boolean.class, true); + assertConverts(false, boolean.class, false); assertConverts((byte) 1, byte.class, (byte) 1); assertConverts('o', char.class, 'o'); assertConverts((short) 1, short.class, (short) 1); @@ -91,6 +95,7 @@ void isAwareOfWideningConversions() { @Test void convertsStringsToPrimitiveTypes() { assertConverts("true", boolean.class, true); + assertConverts("false", boolean.class, false); assertConverts("o", char.class, 'o'); assertConverts("1", byte.class, (byte) 1); assertConverts("1_0", byte.class, (byte) 10); @@ -106,19 +111,75 @@ void convertsStringsToPrimitiveTypes() { assertConverts("42.2_3", double.class, 42.23); } + @Test + void convertsStringsToPrimitiveWrapperTypes() { + assertConverts("true", Boolean.class, true); + assertConverts("false", Boolean.class, false); + assertConverts("o", Character.class, 'o'); + assertConverts("1", Byte.class, (byte) 1); + assertConverts("1_0", Byte.class, (byte) 10); + assertConverts("1", Short.class, (short) 1); + assertConverts("1_2", Short.class, (short) 12); + assertConverts("42", Integer.class, 42); + assertConverts("700_050_000", Integer.class, 700_050_000); + assertConverts("42", Long.class, 42L); + assertConverts("4_2", Long.class, 42L); + assertConverts("42.23", Float.class, 42.23f); + assertConverts("42.2_3", Float.class, 42.23f); + assertConverts("42.23", Double.class, 42.23); + assertConverts("42.2_3", Double.class, 42.23); + } + + @ParameterizedTest(name = "[{index}] {0}") + @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class, + double.class }) + void throwsExceptionForNullToPrimitiveTypeConversion(Class type) { + assertThatExceptionOfType(ArgumentConversionException.class) // + .isThrownBy(() -> convert(null, type)) // + .withMessage("Cannot convert null to primitive value of type " + type.getCanonicalName()); + } + + @ParameterizedTest(name = "[{index}] {0}") + @ValueSource(classes = { Boolean.class, Character.class, Short.class, Byte.class, Integer.class, Long.class, + Float.class, Double.class }) + void throwsExceptionWhenConvertingTheWordNullToPrimitiveWrapperType(Class type) { + assertThatExceptionOfType(ArgumentConversionException.class) // + .isThrownBy(() -> convert("null", type)) // + .withMessage("Failed to convert String \"null\" to type " + type.getCanonicalName()); + assertThatExceptionOfType(ArgumentConversionException.class) // + .isThrownBy(() -> convert("NULL", type)) // + .withMessage("Failed to convert String \"NULL\" to type " + type.getCanonicalName()); + } + @Test void throwsExceptionOnInvalidStringForPrimitiveTypes() { assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert("ab", char.class)) // .withMessage("Failed to convert String \"ab\" to type char") // .havingCause() // + .havingCause() // .withMessage("String must have length of 1: ab"); assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert("tru", boolean.class)) // .withMessage("Failed to convert String \"tru\" to type boolean") // .havingCause() // + .havingCause() // .withMessage("String must be 'true' or 'false' (ignoring case): tru"); + + assertThatExceptionOfType(ArgumentConversionException.class) // + .isThrownBy(() -> convert("null", boolean.class)) // + .withMessage("Failed to convert String \"null\" to type boolean") // + .havingCause() // + .havingCause() // + .withMessage("String must be 'true' or 'false' (ignoring case): null"); + + assertThatExceptionOfType(ArgumentConversionException.class) // + .isThrownBy(() -> convert("NULL", boolean.class)) // + .withMessage("Failed to convert String \"NULL\" to type boolean") // + .havingCause() // + .havingCause() // + .withMessage("String must be 'true' or 'false' (ignoring case): NULL"); } @Test @@ -281,6 +342,7 @@ void convertsStringToCurrency() { } @Test + @SuppressWarnings("deprecation") void convertsStringToLocale() { assertConverts("en", Locale.class, Locale.ENGLISH); assertConverts("en_us", Locale.class, new Locale(Locale.US.toString())); diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 767768ce6bac..e932451fa685 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -248,11 +248,12 @@ void customEmptyValueAndDefaultNullValue() { @Test void customNullValues() { - var annotation = csvSource().nullValues("N/A", "NIL").lines("apple, , NIL, '', N/A, banana").build(); + var annotation = csvSource().nullValues("N/A", "NIL", "null")// + .lines("apple, , NIL, '', N/A, banana, null").build(); var arguments = provideArguments(annotation); - assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana")); + assertThat(arguments).containsExactly(array("apple", null, null, "", null, "banana", null)); } @Test diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java new file mode 100644 index 000000000000..439334635776 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support.conversion; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; + +/** + * {@code ConversionException} is an exception that can occur when an + * object is converted to another object. + * + * @since 1.11 + */ +@API(status = EXPERIMENTAL, since = "1.11") +public class ConversionException extends JUnitException { + + private static final long serialVersionUID = 1L; + + public ConversionException(String message) { + super(message); + } + + public ConversionException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java similarity index 98% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java index 79f0028e58a6..06dc29153e31 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.findConstructors; @@ -48,8 +48,8 @@ * If neither a single factory method nor a single constructor is found, this * converter acts as a no-op. * - * @since 5.1 - * @see DefaultArgumentConverter + * @since 1.11 + * @see StringConversionSupport */ class FallbackStringToObjectConverter implements StringToObjectConverter { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringConversionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringConversionSupport.java new file mode 100644 index 000000000000..2ac35dc67461 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringConversionSupport.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support.conversion; + +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.junit.platform.commons.util.ReflectionUtils.getWrapperType; + +import java.io.File; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URI; +import java.net.URL; +import java.util.Currency; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.UUID; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.ClassLoaderUtils; + +/** + * {@code StringConversionSupport} is able to convert from strings to a number + * of primitive types and their corresponding wrapper types (Byte, Short, + * Integer, Long, Float, and Double), date and time types from the + * {@code java.time} package, and some additional common Java types such as + * {@link File}, {@link BigDecimal}, {@link BigInteger}, {@link Currency}, + * {@link Locale}, {@link URI}, {@link URL}, {@link UUID}, etc. + * + *

If the target type is {@code String} the source {@code String} will not + * be modified. + * + * @since 1.11 + */ +@API(status = EXPERIMENTAL, since = "1.11") +public final class StringConversionSupport { + + private static final List stringToObjectConverters = unmodifiableList(asList( // + new StringToBooleanConverter(), // + new StringToCharacterConverter(), // + new StringToNumberConverter(), // + new StringToClassConverter(), // + new StringToEnumConverter(), // + new StringToJavaTimeConverter(), // + new StringToCommonJavaTypesConverter(), // + new FallbackStringToObjectConverter() // + )); + + private StringConversionSupport() { + /* no-op */ + } + + /** + * Convert a {@code String} into an object of the supplied type. + * + *

Some underlying converters can require a {@code ClassLoader}. + * If none is provided, the default one given by + * {@link ClassLoaderUtils#getDefaultClassLoader()} will be used. + * + * @param source the source {@code String} to convert; may be {@code null} + * @param targetType the target type the source should be converted into; + * never {@code null} + * @param classLoader the {@code ClassLoader} to use; may be {@code null} + * @param the type of the target + * @return the converted object; may be {@code null} but only if the target + * type is a reference type + * + * @since 1.11 + */ + @SuppressWarnings("unchecked") + public static T convert(String source, Class targetType, ClassLoader classLoader) { + if (source == null) { + if (targetType.isPrimitive()) { + throw new ConversionException( + "Cannot convert null to primitive value of type " + targetType.getTypeName()); + } + return null; + } + + if (String.class.equals(targetType)) { + return (T) source; + } + + Class targetTypeToUse = toWrapperType(targetType); + Optional converter = stringToObjectConverters.stream().filter( + candidate -> candidate.canConvert(targetTypeToUse)).findFirst(); + if (converter.isPresent()) { + try { + ClassLoader classLoaderToUse = classLoader != null ? classLoader + : ClassLoaderUtils.getDefaultClassLoader(); + return (T) converter.get().convert(source, targetTypeToUse, classLoaderToUse); + } + catch (Exception ex) { + if (ex instanceof ConversionException) { + // simply rethrow it + throw (ConversionException) ex; + } + // else + throw new ConversionException( + String.format("Failed to convert String \"%s\" to type %s", source, targetType.getTypeName()), ex); + } + } + + throw new ConversionException( + "No built-in converter for source type java.lang.String and target type " + targetType.getTypeName()); + } + + private static Class toWrapperType(Class targetType) { + Class wrapperType = getWrapperType(targetType); + return wrapperType != null ? wrapperType : targetType; + } + +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToBooleanConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java similarity index 93% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToBooleanConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java index 9d911825809b..2bde9ac323c2 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToBooleanConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import org.junit.platform.commons.util.Preconditions; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCharacterConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java similarity index 93% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCharacterConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java index b3849051e45d..925acdfe62d5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCharacterConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import org.junit.platform.commons.util.Preconditions; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToClassConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java similarity index 90% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToClassConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java index debefc342ed0..df2b0164ad5c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToClassConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import org.junit.platform.commons.util.ReflectionUtils; @@ -28,7 +28,7 @@ public Object convert(String source, Class targetType) throws Exception { public Object convert(String className, Class targetType, ClassLoader classLoader) throws Exception { // @formatter:off return ReflectionUtils.tryToLoadClass(className, classLoader) - .getOrThrow(cause -> new ArgumentConversionException( + .getOrThrow(cause -> new ConversionException( "Failed to convert String \"" + className + "\" to type java.lang.Class", cause)); // @formatter:on } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCommonJavaTypesConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java similarity index 92% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCommonJavaTypesConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java index b9ac124aded8..2988714318e3 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToCommonJavaTypesConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import static java.util.Collections.unmodifiableMap; @@ -63,7 +63,7 @@ private static URL toURL(String url) { return URI.create(url).toURL(); } catch (MalformedURLException ex) { - throw new ArgumentConversionException("Failed to convert String \"" + url + "\" to type java.net.URL", ex); + throw new ConversionException("Failed to convert String \"" + url + "\" to type java.net.URL", ex); } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToEnumConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java similarity index 92% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToEnumConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java index f20d1487d5ee..48c07fa59eb7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToEnumConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; class StringToEnumConverter implements StringToObjectConverter { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToJavaTimeConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java similarity index 97% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToJavaTimeConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java index b1851ecc323d..6ecbf84b25e5 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToJavaTimeConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import static java.util.Collections.unmodifiableMap; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToNumberConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java similarity index 96% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToNumberConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java index ca278019a488..b8cd6e7d3e4f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToNumberConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import static java.util.Collections.unmodifiableMap; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java similarity index 96% rename from junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToObjectConverter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java index 2a60202dba81..243bfeec4afc 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/StringToObjectConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; /** * Internal API for converting arguments of type {@link String} to a specified diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java new file mode 100644 index 000000000000..e51977179941 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java @@ -0,0 +1,5 @@ +/** + * Maintained conversion APIs provided by the JUnit Platform. + */ + +package org.junit.platform.commons.support.conversion; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java index e6bb3ddd27df..3f7af1ddd407 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java @@ -23,6 +23,7 @@ import java.nio.file.Paths; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -32,7 +33,7 @@ final class CloseablePath implements Closeable { private static final String FILE_URI_SCHEME = "file"; - private static final String JAR_URI_SCHEME = "jar"; + static final String JAR_URI_SCHEME = "jar"; private static final String JAR_FILE_EXTENSION = ".jar"; private static final String JAR_URI_SEPARATOR = "!"; @@ -41,26 +42,34 @@ final class CloseablePath implements Closeable { private static final ConcurrentMap MANAGED_FILE_SYSTEMS = new ConcurrentHashMap<>(); + private final AtomicBoolean closed = new AtomicBoolean(); + private final Path path; private final Closeable delegate; static CloseablePath create(URI uri) throws URISyntaxException { + return create(uri, it -> FileSystems.newFileSystem(it, emptyMap())); + } + + static CloseablePath create(URI uri, FileSystemProvider fileSystemProvider) throws URISyntaxException { if (JAR_URI_SCHEME.equals(uri.getScheme())) { String[] parts = uri.toString().split(JAR_URI_SEPARATOR); String jarUri = parts[0]; String jarEntry = parts[1]; - return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry)); + return createForJarFileSystem(new URI(jarUri), fileSystem -> fileSystem.getPath(jarEntry), + fileSystemProvider); } - if (uri.getScheme().equals(FILE_URI_SCHEME) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) { + if (FILE_URI_SCHEME.equals(uri.getScheme()) && uri.getPath().endsWith(JAR_FILE_EXTENSION)) { return createForJarFileSystem(new URI(JAR_URI_SCHEME + ':' + uri), - fileSystem -> fileSystem.getRootDirectories().iterator().next()); + fileSystem -> fileSystem.getRootDirectories().iterator().next(), fileSystemProvider); } return new CloseablePath(Paths.get(uri), NULL_CLOSEABLE); } - private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider) { + private static CloseablePath createForJarFileSystem(URI jarUri, Function pathProvider, + FileSystemProvider fileSystemProvider) { ManagedFileSystem managedFileSystem = MANAGED_FILE_SYSTEMS.compute(jarUri, - (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri) : oldValue.retain()); + (__, oldValue) -> oldValue == null ? new ManagedFileSystem(jarUri, fileSystemProvider) : oldValue.retain()); Path path = pathProvider.apply(managedFileSystem.fileSystem); return new CloseablePath(path, () -> MANAGED_FILE_SYSTEMS.compute(jarUri, (__, ___) -> managedFileSystem.release())); @@ -77,7 +86,9 @@ public Path getPath() { @Override public void close() throws IOException { - delegate.close(); + if (closed.compareAndSet(false, true)) { + delegate.close(); + } } private static class ManagedFileSystem { @@ -86,10 +97,10 @@ private static class ManagedFileSystem { private final FileSystem fileSystem; private final URI jarUri; - ManagedFileSystem(URI jarUri) { + ManagedFileSystem(URI jarUri, FileSystemProvider fileSystemProvider) { this.jarUri = jarUri; try { - fileSystem = FileSystems.newFileSystem(jarUri, emptyMap()); + fileSystem = fileSystemProvider.newFileSystem(jarUri); } catch (IOException e) { throw new UncheckedIOException("Failed to create file system for " + jarUri, e); @@ -118,4 +129,8 @@ private void close() { } } } + + interface FileSystemProvider { + FileSystem newFileSystem(URI uri) throws IOException; + } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 816b7fadd104..b7e45171dbaf 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -927,57 +927,6 @@ else if (methodPart.endsWith(")")) { return new String[] { className, methodName, methodParameters }; } - /** - * Get the outermost instance of the required type, searching recursively - * through enclosing instances. - * - *

If the supplied inner object is of the required type, it will be - * returned. - * - * @param inner the inner object from which to begin the search; never {@code null} - * @param requiredType the required type of the outermost instance; never {@code null} - * @return an {@code Optional} containing the outermost instance; never {@code null} - * but potentially empty - * @deprecated Please discontinue use of this method since it relies on internal - * implementation details of the JDK that may not work in the future. - */ - @API(status = DEPRECATED, since = "1.4") - @Deprecated - public static Optional getOutermostInstance(Object inner, Class requiredType) { - Preconditions.notNull(inner, "inner object must not be null"); - Preconditions.notNull(requiredType, "requiredType must not be null"); - - if (requiredType.isInstance(inner)) { - return Optional.of(inner); - } - - Optional candidate = getOuterInstance(inner); - if (candidate.isPresent()) { - return getOutermostInstance(candidate.get(), requiredType); - } - - return Optional.empty(); - } - - private static Optional getOuterInstance(Object inner) { - // This is risky since it depends on the name of the field which is nowhere guaranteed - // but has been stable so far in all JDKs - - // @formatter:off - return Arrays.stream(inner.getClass().getDeclaredFields()) - .filter(field -> field.getName().startsWith("this$")) - .findFirst() - .map(field -> { - try { - return makeAccessible(field).get(inner); - } - catch (Throwable t) { - throw ExceptionUtils.throwAsUncheckedException(t); - } - }); - // @formatter:on - } - public static Set getAllClasspathRootDirectories() { // This is quite a hack, since sometimes the classpath is quite different String fullClassPath = System.getProperty("java.class.path"); @@ -1238,6 +1187,7 @@ public static List> findConstructors(Class clazz, Predicate findFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { + return streamFields(clazz, predicate, traversalMode).collect(toUnmodifiableList()); } @@ -1252,21 +1202,23 @@ public static Stream streamFields(Class clazz, Predicate predic Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - return findAllFieldsInHierarchy(clazz, traversalMode).stream().filter(predicate); + return findAllFieldsInHierarchy(clazz, predicate, traversalMode).stream(); } - private static List findAllFieldsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { + private static List findAllFieldsInHierarchy(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off - List localFields = getDeclaredFields(clazz).stream() + List localFields = getDeclaredFields(clazz, predicate).stream() .filter(field -> !field.isSynthetic()) .collect(toList()); - List superclassFields = getSuperclassFields(clazz, traversalMode).stream() + List superclassFields = getSuperclassFields(clazz, predicate, traversalMode).stream() .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) .collect(toList()); - List interfaceFields = getInterfaceFields(clazz, traversalMode).stream() + List interfaceFields = getInterfaceFields(clazz, predicate, traversalMode).stream() .filter(field -> !isFieldShadowedByLocalFields(field, localFields)) .collect(toList()); // @formatter:on @@ -1404,11 +1356,11 @@ private static Optional findMethod(Class clazz, Predicate pre for (Class current = clazz; isSearchable(current); current = current.getSuperclass()) { // Search for match in current type - List methods = current.isInterface() ? getMethods(current) : getDeclaredMethods(current, BOTTOM_UP); - for (Method method : methods) { - if (predicate.test(method)) { - return Optional.of(method); - } + List methods = current.isInterface() ? getMethods(current, predicate) + : getDeclaredMethods(current, predicate, BOTTOM_UP); + if (!methods.isEmpty()) { + // Since the predicate has already been applied, return the first match. + return Optional.of(methods.get(0)); } // Search for match in interfaces implemented by current type @@ -1489,29 +1441,27 @@ public static Stream streamMethods(Class clazz, Predicate pre Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - // @formatter:off - return findAllMethodsInHierarchy(clazz, traversalMode).stream() - .filter(predicate) - .distinct(); - // @formatter:on + return findAllMethodsInHierarchy(clazz, predicate, traversalMode).stream().distinct(); } /** * Find all non-synthetic methods in the superclass and interface hierarchy, - * excluding Object. + * excluding Object, that match the specified {@code predicate}. */ - private static List findAllMethodsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { + private static List findAllMethodsInHierarchy(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); // @formatter:off - List localMethods = getDeclaredMethods(clazz, traversalMode).stream() + List localMethods = getDeclaredMethods(clazz, predicate, traversalMode).stream() .filter(method -> !method.isSynthetic()) .collect(toList()); - List superclassMethods = getSuperclassMethods(clazz, traversalMode).stream() + List superclassMethods = getSuperclassMethods(clazz, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); - List interfaceMethods = getInterfaceMethods(clazz, traversalMode).stream() + List interfaceMethods = getInterfaceMethods(clazz, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localMethods)) .collect(toList()); // @formatter:on @@ -1531,40 +1481,42 @@ private static List findAllMethodsInHierarchy(Class clazz, HierarchyT /** * Custom alternative to {@link Class#getFields()} that sorts the fields - * and converts them to a mutable list. + * which match the supplied predicate and converts them to a mutable list. */ - private static List getFields(Class clazz) { - return toSortedMutableList(clazz.getFields()); + private static List getFields(Class clazz, Predicate predicate) { + return toSortedMutableList(clazz.getFields(), predicate); } /** * Custom alternative to {@link Class#getDeclaredFields()} that sorts the - * fields and converts them to a mutable list. + * fields which match the supplied predicate and converts them to a mutable list. */ - private static List getDeclaredFields(Class clazz) { - return toSortedMutableList(clazz.getDeclaredFields()); + private static List getDeclaredFields(Class clazz, Predicate predicate) { + return toSortedMutableList(clazz.getDeclaredFields(), predicate); } /** * Custom alternative to {@link Class#getMethods()} that sorts the methods - * and converts them to a mutable list. + * which match the supplied predicate and converts them to a mutable list. */ - private static List getMethods(Class clazz) { - return toSortedMutableList(clazz.getMethods()); + private static List getMethods(Class clazz, Predicate predicate) { + return toSortedMutableList(clazz.getMethods(), predicate); } /** * Custom alternative to {@link Class#getDeclaredMethods()} that sorts the - * methods and converts them to a mutable list. + * methods which match the supplied predicate and converts them to a mutable list. * *

In addition, the list returned by this method includes interface * default methods which are either prepended or appended to the list of * declared methods depending on the supplied traversal mode. */ - private static List getDeclaredMethods(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getDeclaredMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + // Note: getDefaultMethods() already sorts the methods, - List defaultMethods = getDefaultMethods(clazz); - List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods()); + List defaultMethods = getDefaultMethods(clazz, predicate); + List declaredMethods = toSortedMutableList(clazz.getDeclaredMethods(), predicate); // Take the traversal mode into account in order to retain the inherited // nature of interface default methods. @@ -1581,41 +1533,43 @@ private static List getDeclaredMethods(Class clazz, HierarchyTraversa /** * Get a sorted, mutable list of all default methods present in interfaces * implemented by the supplied class which are also visible within - * the supplied class. + * the supplied class and match the supplied predicate. * * @see Method Visibility * in the Java Language Specification */ - private static List getDefaultMethods(Class clazz) { + private static List getDefaultMethods(Class clazz, Predicate predicate) { // @formatter:off // Visible default methods are interface default methods that have not // been overridden. List visibleDefaultMethods = Arrays.stream(clazz.getMethods()) - .filter(Method::isDefault) + .filter(predicate.and(Method::isDefault)) .collect(toCollection(ArrayList::new)); if (visibleDefaultMethods.isEmpty()) { return visibleDefaultMethods; } return Arrays.stream(clazz.getInterfaces()) - .map(ReflectionUtils::getMethods) + .map(ifc -> getMethods(ifc, predicate)) .flatMap(List::stream) .filter(visibleDefaultMethods::contains) .collect(toCollection(ArrayList::new)); // @formatter:on } - private static List toSortedMutableList(Field[] fields) { + private static List toSortedMutableList(Field[] fields, Predicate predicate) { // @formatter:off return Arrays.stream(fields) + .filter(predicate) .sorted(ReflectionUtils::defaultFieldSorter) // Use toCollection() instead of toList() to ensure list is mutable. .collect(toCollection(ArrayList::new)); // @formatter:on } - private static List toSortedMutableList(Method[] methods) { + private static List toSortedMutableList(Method[] methods, Predicate predicate) { // @formatter:off return Arrays.stream(methods) + .filter(predicate) .sorted(ReflectionUtils::defaultMethodSorter) // Use toCollection() instead of toList() to ensure list is mutable. .collect(toCollection(ArrayList::new)); @@ -1647,16 +1601,18 @@ private static int defaultMethodSorter(Method method1, Method method2) { return comparison; } - private static List getInterfaceMethods(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getInterfaceMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + List allInterfaceMethods = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { // @formatter:off - List localInterfaceMethods = getMethods(ifc).stream() - .filter(m -> !isAbstract(m)) + List localInterfaceMethods = getMethods(ifc, predicate).stream() + .filter(method -> !isAbstract(method)) .collect(toList()); - List superinterfaceMethods = getInterfaceMethods(ifc, traversalMode).stream() + List superinterfaceMethods = getInterfaceMethods(ifc, predicate, traversalMode).stream() .filter(method -> !isMethodShadowedByLocalMethods(method, localInterfaceMethods)) .collect(toList()); // @formatter:on @@ -1672,13 +1628,15 @@ private static List getInterfaceMethods(Class clazz, HierarchyTravers return allInterfaceMethods; } - private static List getInterfaceFields(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getInterfaceFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + List allInterfaceFields = new ArrayList<>(); for (Class ifc : clazz.getInterfaces()) { - List localInterfaceFields = getFields(ifc); + List localInterfaceFields = getFields(ifc, predicate); // @formatter:off - List superinterfaceFields = getInterfaceFields(ifc, traversalMode).stream() + List superinterfaceFields = getInterfaceFields(ifc, predicate, traversalMode).stream() .filter(field -> !isFieldShadowedByLocalFields(field, localInterfaceFields)) .collect(toList()); // @formatter:on @@ -1694,24 +1652,28 @@ private static List getInterfaceFields(Class clazz, HierarchyTraversal return allInterfaceFields; } - private static List getSuperclassFields(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getSuperclassFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Class superclass = clazz.getSuperclass(); if (!isSearchable(superclass)) { return Collections.emptyList(); } - return findAllFieldsInHierarchy(superclass, traversalMode); + return findAllFieldsInHierarchy(superclass, predicate, traversalMode); } private static boolean isFieldShadowedByLocalFields(Field field, List localFields) { return localFields.stream().anyMatch(local -> local.getName().equals(field.getName())); } - private static List getSuperclassMethods(Class clazz, HierarchyTraversalMode traversalMode) { + private static List getSuperclassMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Class superclass = clazz.getSuperclass(); if (!isSearchable(superclass)) { return Collections.emptyList(); } - return findAllMethodsInHierarchy(superclass, traversalMode); + return findAllMethodsInHierarchy(superclass, predicate, traversalMode); } private static boolean isMethodShadowedByLocalMethods(Method method, List localMethods) { diff --git a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java index f33ffd314feb..774684198f9f 100644 --- a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java +++ b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java @@ -36,6 +36,7 @@ org.junit.platform.testkit, org.junit.vintage.engine; exports org.junit.platform.commons.support; + exports org.junit.platform.commons.support.conversion; exports org.junit.platform.commons.util to org.junit.jupiter.api, org.junit.jupiter.engine, diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java index 401fa19363c8..930b817e1b92 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java @@ -54,6 +54,8 @@ public interface TestEngine { * and JUnit Jupiter use {@code "junit-vintage"} and {@code "junit-jupiter"}, * respectively. When in doubt, you may use the fully qualified name of your * custom {@code TestEngine} implementation class. + * + * @return the ID of this test engine; never {@code null} or blank */ String getId(); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java index 5e4f13b99661..f60e0db8b588 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java @@ -118,7 +118,7 @@ final Optional getRoot() { * @see #forEngine(String) */ public final Optional getEngineId() { - return getRoot().filter(segment -> segment.getType().equals(ENGINE_SEGMENT_TYPE)).map(Segment::getValue); + return getRoot().filter(segment -> ENGINE_SEGMENT_TYPE.equals(segment.getType())).map(Segment::getValue); } /** 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 536c029b10a0..2cc1af6211c9 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 @@ -252,7 +252,8 @@ private T castToRequiredType(Object key, Object value, Class requiredType } // else throw new NamespacedHierarchicalStoreException( - String.format("Object stored under key [%s] is not of required type [%s]", key, requiredType.getName())); + String.format("Object stored under key [%s] is not of required type [%s], but was [%s]: %s", key, + requiredType.getName(), value.getClass().getName(), value)); } private static class CompositeKey { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java index abeeb298acaa..77ff53d83af7 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java @@ -16,6 +16,7 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestEngine; /** @@ -29,7 +30,7 @@ private EngineIdValidator() { static Iterable validate(Iterable testEngines) { Set ids = new HashSet<>(); for (TestEngine testEngine : testEngines) { - // check usage of reserved id prefix + // check usage of reserved ID prefix if (!validateReservedIds(testEngine)) { getLogger().warn(() -> String.format( "Third-party TestEngine implementations are forbidden to use the reserved 'junit-' prefix for their ID: '%s'", @@ -52,23 +53,27 @@ private static Logger getLogger() { // https://github.com/junit-team/junit5/issues/1557 private static boolean validateReservedIds(TestEngine testEngine) { - String engineId = testEngine.getId(); + String engineId = Preconditions.notBlank(testEngine.getId(), + () -> String.format("ID for TestEngine [%s] must not be null or blank", testEngine.getClass().getName())); if (!engineId.startsWith("junit-")) { return true; } - if (engineId.equals("junit-jupiter")) { - validateWellKnownClassName(testEngine, "org.junit.jupiter.engine.JupiterTestEngine"); - return true; - } - if (engineId.equals("junit-vintage")) { - validateWellKnownClassName(testEngine, "org.junit.vintage.engine.VintageTestEngine"); - return true; - } - if (engineId.equals("junit-platform-suite")) { - validateWellKnownClassName(testEngine, "org.junit.platform.suite.engine.SuiteTestEngine"); - return true; + switch (engineId) { + case "junit-jupiter": { + validateWellKnownClassName(testEngine, "org.junit.jupiter.engine.JupiterTestEngine"); + return true; + } + case "junit-vintage": { + validateWellKnownClassName(testEngine, "org.junit.vintage.engine.VintageTestEngine"); + return true; + } + case "junit-platform-suite": { + validateWellKnownClassName(testEngine, "org.junit.platform.suite.engine.SuiteTestEngine"); + return true; + } + default: + return false; } - return false; } private static void validateWellKnownClassName(TestEngine testEngine, String expectedClassName) { @@ -80,4 +85,5 @@ private static void validateWellKnownClassName(TestEngine testEngine, String exp String.format("Third-party TestEngine '%s' is forbidden to use the reserved '%s' TestEngine ID.", actualClassName, testEngine.getId())); } + } diff --git a/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java b/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java index 9501caff5a6a..9d79f3f4166a 100644 --- a/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java +++ b/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java @@ -16,6 +16,8 @@ * @since 1.0 * @uses org.junit.platform.engine.TestEngine * @uses org.junit.platform.launcher.LauncherDiscoveryListener + * @uses org.junit.platform.launcher.LauncherInterceptor + * @uses org.junit.platform.launcher.LauncherSessionListener * @uses org.junit.platform.launcher.PostDiscoveryFilter * @uses org.junit.platform.launcher.TestExecutionListener */ @@ -32,6 +34,7 @@ uses org.junit.platform.engine.TestEngine; uses org.junit.platform.launcher.LauncherDiscoveryListener; + uses org.junit.platform.launcher.LauncherInterceptor; uses org.junit.platform.launcher.LauncherSessionListener; uses org.junit.platform.launcher.PostDiscoveryFilter; uses org.junit.platform.launcher.TestExecutionListener; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java index 0ad3c5f9b6a0..03ae08dd0a6c 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java @@ -43,12 +43,14 @@ public class RunnerTestDescriptor extends VintageTestDescriptor { private final Set rejectedExclusions = new HashSet<>(); private Runner runner; + private final boolean ignored; private boolean wasFiltered; private List filters = new ArrayList<>(); - public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner) { + public RunnerTestDescriptor(UniqueId uniqueId, Class testClass, Runner runner, boolean ignored) { super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass)); this.runner = runner; + this.ignored = ignored; } @Override @@ -155,6 +157,10 @@ private Runner getRunnerToReport() { return (runner instanceof RunnerDecorator) ? ((RunnerDecorator) runner).getDecoratedRunner() : runner; } + public boolean isIgnored() { + return ignored; + } + private static class ExcludeDescriptionFilter extends Filter { private final Description description; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java index a2d1e7d4c80c..14445f155b4e 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java @@ -26,7 +26,6 @@ import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.support.discovery.SelectorResolver; import org.junit.runner.Runner; -import org.junit.runners.model.RunnerBuilder; import org.junit.vintage.engine.descriptor.RunnerTestDescriptor; /** @@ -34,7 +33,7 @@ */ class ClassSelectorResolver implements SelectorResolver { - private static final RunnerBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); + private static final DefensiveAllDefaultPossibilitiesBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder(); private final ClassFilter classFilter; @@ -76,7 +75,7 @@ private Resolution resolveTestClass(Class testClass, Context context) { private RunnerTestDescriptor createRunnerTestDescriptor(TestDescriptor parent, Class testClass, Runner runner) { UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - return new RunnerTestDescriptor(uniqueId, testClass, runner); + return new RunnerTestDescriptor(uniqueId, testClass, runner, RUNNER_BUILDER.isIgnored(runner)); } } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java index 0da94f689fde..b35dc20077c4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java @@ -64,6 +64,10 @@ public Runner runnerForClass(Class testClass) throws Throwable { return runner; } + boolean isIgnored(Runner runner) { + return runner instanceof IgnoredClassRunner || runner instanceof IgnoringRunnerDecorator; + } + /** * Instead of checking for the {@link Ignore} annotation and returning an * {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the @@ -72,7 +76,7 @@ public Runner runnerForClass(Class testClass) throws Throwable { * override its runtime behavior (i.e. skip execution) but return its * regular {@link org.junit.runner.Description}. */ - private Runner decorateIgnoredTestClass(Runner runner) { + private IgnoringRunnerDecorator decorateIgnoredTestClass(Runner runner) { if (runner instanceof Filterable) { return new FilterableIgnoringRunnerDecorator(runner); } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java index 9cefb8faee30..58144877d04a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -20,6 +20,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.notification.Failure; @@ -49,7 +50,7 @@ class RunListenerAdapter extends RunListener { @Override public void testRunStarted(Description description) { - if (description.isSuite() && description.getAnnotation(Ignore.class) == null) { + if (description.isSuite() && !testRun.getRunnerTestDescriptor().isIgnored()) { fireExecutionStarted(testRun.getRunnerTestDescriptor(), EventType.REPORTED); } } @@ -65,7 +66,9 @@ public void testSuiteStarted(Description description) { @Override public void testIgnored(Description description) { - testIgnored(lookupOrRegisterNextTestDescriptor(description), determineReasonForIgnoredTest(description)); + TestDescriptor testDescriptor = lookupOrRegisterNextTestDescriptor(description); + String reason = determineReasonForIgnoredTest(testDescriptor, description).orElse(""); + testIgnored(testDescriptor, reason); } @Override @@ -176,9 +179,21 @@ private void testIgnored(TestDescriptor testDescriptor, String reason) { fireExecutionSkipped(testDescriptor, reason); } - private String determineReasonForIgnoredTest(Description description) { - Ignore ignoreAnnotation = description.getAnnotation(Ignore.class); - return Optional.ofNullable(ignoreAnnotation).map(Ignore::value).orElse(""); + private Optional determineReasonForIgnoredTest(TestDescriptor testDescriptor, Description description) { + Optional reason = getReason(description.getAnnotation(Ignore.class)); + if (reason.isPresent()) { + return reason; + } + // Workaround for some runners (e.g. JUnit38ClassRunner) don't include the @Ignore annotation + // in the description, so we read it from the test class directly + return testDescriptor.getSource() // + .filter(ClassSource.class::isInstance) // + .map(source -> ((ClassSource) source).getJavaClass()) // + .flatMap(testClass -> getReason(testClass.getAnnotation(Ignore.class))); + } + + private static Optional getReason(Ignore annotation) { + return Optional.ofNullable(annotation).map(Ignore::value); } private void dynamicTestRegistered(TestDescriptor testDescriptor) { diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java index cb12669aef13..e6eea6bff78b 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java @@ -151,9 +151,8 @@ void removesCompleteClassIfItHasExcludedTag() { .containsExactly("JUnit Vintage"); } - @TrackLogRecords @Test - void executesAllTestsForNotFilterableRunner(LogRecordListener logRecordListener) { + void executesAllTestsForNotFilterableRunner(@TrackLogRecords LogRecordListener logRecordListener) { Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; var request = request() // .selectors(selectClass(testClass)) // @@ -172,9 +171,8 @@ void executesAllTestsForNotFilterableRunner(LogRecordListener logRecordListener) + " does not support filtering and will therefore be run completely."); } - @TrackLogRecords @Test - void executesAllTestsForNotFilterableChildRunnerOfSuite(LogRecordListener logRecordListener) { + void executesAllTestsForNotFilterableChildRunnerOfSuite(@TrackLogRecords LogRecordListener logRecordListener) { Class suiteClass = JUnit4SuiteOfSuiteWithFilterableChildRunner.class; Class testClass = JUnit4TestCaseWithNotFilterableRunner.class; var request = request() // @@ -194,9 +192,9 @@ void executesAllTestsForNotFilterableChildRunnerOfSuite(LogRecordListener logRec + " was not able to satisfy all filter requests."); } - @TrackLogRecords @Test - void executesAllTestsWhenFilterDidNotExcludeTestForJUnit3Suite(LogRecordListener logRecordListener) { + void executesAllTestsWhenFilterDidNotExcludeTestForJUnit3Suite( + @TrackLogRecords LogRecordListener logRecordListener) { Class suiteClass = JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.class; Class testClass = PlainJUnit3TestCaseWithSingleTestWhichFails.class; var request = request() // diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index 66af815aff99..cd33b52e2269 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -10,6 +10,7 @@ package org.junit.vintage.engine; +import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; @@ -56,8 +57,10 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; +import org.junit.vintage.engine.samples.junit3.IgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites; import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites; +import org.junit.vintage.engine.samples.junit3.JUnit4SuiteWithIgnoredJUnit3TestCase; import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails; import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase; import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase; @@ -896,6 +899,27 @@ void executesRegularSpockFeatureMethod() { event(engine(), finishedSuccessfully())); } + @Test + void executesIgnoredJUnit3TestCase() { + var suiteClass = IgnoredJUnit3TestCase.class; + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), skippedWithReason(isEqual("testing"))), // + event(engine(), finishedSuccessfully())); + } + + @Test + void executesJUnit4SuiteWithIgnoredJUnit3TestCase() { + var suiteClass = JUnit4SuiteWithIgnoredJUnit3TestCase.class; + var testClass = IgnoredJUnit3TestCase.class; + execute(suiteClass).allEvents().assertEventsMatchExactly( // + event(engine(), started()), // + event(container(suiteClass), started()), // + event(container(testClass), skippedWithReason(isEqual("testing"))), // + event(container(suiteClass), finishedSuccessfully()), // + event(engine(), finishedSuccessfully())); + } + private static EngineExecutionResults execute(Class testClass) { return execute(request(testClass)); } diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java index 69272fd511db..918e32dffa96 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java @@ -32,7 +32,8 @@ class TestRunTests { void returnsEmptyOptionalForUnknownDescriptions() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); + var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass), + false); var unknownDescription = createTestDescription(testClass, "dynamicTest"); var testRun = new TestRun(runnerTestDescriptor); @@ -45,7 +46,8 @@ void returnsEmptyOptionalForUnknownDescriptions() throws Exception { void registersDynamicTestDescriptors() throws Exception { Class testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class; var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName()); - var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass)); + var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass), + false); var dynamicTestId = runnerId.append(SEGMENT_TYPE_DYNAMIC, "dynamicTest"); var dynamicDescription = createTestDescription(testClass, "dynamicTest"); var dynamicTestDescriptor = new VintageTestDescriptor(dynamicTestId, dynamicDescription, null); diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java new file mode 100644 index 000000000000..64015bf57d52 --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import junit.framework.TestCase; + +import org.junit.Assert; +import org.junit.Ignore; + +/** + * @since 4.12 + */ +@Ignore("testing") +public class IgnoredJUnit3TestCase extends TestCase { + + public void test() { + Assert.fail("this test should be ignored"); + } + +} diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java new file mode 100644 index 000000000000..ba89153ace0c --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit3; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ IgnoredJUnit3TestCase.class }) +public class JUnit4SuiteWithIgnoredJUnit3TestCase { +} diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java similarity index 95% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java rename to platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java index 1d2cd21a6fdf..efc98701df7d 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/FallbackStringToObjectConverterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.jupiter.params.converter; +package org.junit.platform.commons.support.conversion; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; @@ -19,15 +19,15 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.converter.FallbackStringToObjectConverter.IsFactoryConstructor; -import org.junit.jupiter.params.converter.FallbackStringToObjectConverter.IsFactoryMethod; +import org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.IsFactoryConstructor; +import org.junit.platform.commons.support.conversion.FallbackStringToObjectConverter.IsFactoryMethod; import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link FallbackStringToObjectConverter}, {@link IsFactoryMethod}, * and {@link IsFactoryConstructor}. * - * @since 5.1 + * @since 1.11 (originally since JUnit Jupiter 5.1) */ class FallbackStringToObjectConverterTests { diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java index 478491014be1..5dde2c34cbd9 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java @@ -36,13 +36,22 @@ import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.function.Predicate; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.pkg1.ClassLevelDir; +import org.junit.platform.commons.util.pkg1.InstanceLevelDir; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateTempDirField; /** * Unit tests for {@link AnnotationUtils}. @@ -380,6 +389,28 @@ void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() th assertThat(methods.subList(1, 3)).containsOnly(method1, method3); } + /** + * @see https://github.com/junit-team/junit5/issues/3498 + */ + @Test + void findAnnotatedMethodsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String BEFORE = "before"; + Class superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class; + Method beforeAllMethod = superclass.getDeclaredMethod(BEFORE); + Class subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class; + Method beforeEachMethod = subclass.getDeclaredMethod(BEFORE); + + // Prerequisite + var methods = findAnnotatedMethods(superclass, BeforeAll.class, TOP_DOWN); + assertThat(methods).containsExactly(beforeAllMethod); + + // Actual use cases for this test + methods = findAnnotatedMethods(subclass, BeforeAll.class, TOP_DOWN); + assertThat(methods).containsExactly(beforeAllMethod); + methods = findAnnotatedMethods(subclass, BeforeEach.class, TOP_DOWN); + assertThat(methods).containsExactly(beforeEachMethod); + } + @Test void findAnnotatedMethodsForAnnotationUsedInInterface() throws Exception { var interfaceMethod = InterfaceWithAnnotatedDefaultMethod.class.getDeclaredMethod("interfaceMethod"); @@ -477,6 +508,28 @@ private List findShadowingAnnotatedFields(Class ann return findAnnotatedFields(ClassWithShadowedAnnotatedFields.class, annotationType, isStringField); } + /** + * @see https://github.com/junit-team/junit5/issues/3532 + */ + @Test + void findAnnotatedFieldsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String TEMP_DIR = "tempDir"; + Class superclass = SuperclassWithStaticPackagePrivateTempDirField.class; + Field staticField = superclass.getDeclaredField(TEMP_DIR); + Class subclass = SubclassWithNonStaticPackagePrivateTempDirField.class; + Field nonStaticField = subclass.getDeclaredField(TEMP_DIR); + + // Prerequisite + var fields = findAnnotatedFields(superclass, ClassLevelDir.class, field -> true); + assertThat(fields).containsExactly(staticField); + + // Actual use cases for this test + fields = findAnnotatedFields(subclass, ClassLevelDir.class, field -> true); + assertThat(fields).containsExactly(staticField); + fields = findAnnotatedFields(subclass, InstanceLevelDir.class, field -> true); + assertThat(fields).containsExactly(nonStaticField); + } + // === findPublicAnnotatedFields() ========================================= @Test diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java new file mode 100644 index 000000000000..88a2f9acb9b3 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; +import static org.junit.platform.commons.util.CloseablePath.JAR_URI_SCHEME; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.only; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.CloseablePath.FileSystemProvider; +import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; + +class CloseablePathTests { + + URI uri; + URI jarUri; + + List paths = new ArrayList<>(); + + @BeforeEach + void createUris() throws Exception { + uri = getClass().getResource("/jartest.jar").toURI(); + jarUri = URI.create(JAR_URI_SCHEME + ':' + uri); + } + + @AfterEach + void closeAllPaths() { + closeAll(paths); + } + + @Test + void createsAndClosesJarFileSystemOnceWhenCalledConcurrently() throws Exception { + var numThreads = 50; + + FileSystemProvider fileSystemProvider = mock(); + when(fileSystemProvider.newFileSystem(any())) // + .thenAnswer(invocation -> FileSystems.newFileSystem((URI) invocation.getArgument(0), Map.of())); + + paths = executeConcurrently(numThreads, () -> CloseablePath.create(uri, fileSystemProvider)); + verify(fileSystemProvider, only()).newFileSystem(jarUri); + + // Close all but the first path + closeAll(paths.subList(1, numThreads)); + assertDoesNotThrow(() -> FileSystems.getFileSystem(jarUri), "FileSystem should still be open"); + + // Close last remaining path + paths.get(0).close(); + assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), + "FileSystem should have been closed"); + } + + @Test + @SuppressWarnings("resource") + void closingIsIdempotent() throws Exception { + var path1 = CloseablePath.create(uri); + paths.add(path1); + var path2 = CloseablePath.create(uri); + paths.add(path2); + + path1.close(); + path1.close(); + assertDoesNotThrow(() -> FileSystems.getFileSystem(jarUri), "FileSystem should still be open"); + + path2.close(); + assertThrows(FileSystemNotFoundException.class, () -> FileSystems.getFileSystem(jarUri), + "FileSystem should have been closed"); + } + + private static void closeAll(List paths) { + var throwableCollector = new OpenTest4JAwareThrowableCollector(); + paths.forEach(closeablePath -> throwableCollector.execute(closeablePath::close)); + throwableCollector.assertEmpty(); + } +} 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 d74b74ce7592..136cb77a71e8 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 @@ -25,6 +25,7 @@ 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; import static org.junit.platform.commons.util.ReflectionUtils.findMethod; import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; @@ -72,6 +73,10 @@ import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClass.StaticNestedSiblingClass; import org.junit.platform.commons.util.ReflectionUtilsTests.OuterClassImplementingInterface.InnerClassImplementingInterface; import org.junit.platform.commons.util.classes.CustomType; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateBeforeMethod; +import org.junit.platform.commons.util.pkg1.subpkg.SubclassWithNonStaticPackagePrivateTempDirField; /** * Unit tests for {@link ReflectionUtils}. @@ -702,31 +707,6 @@ void parseFullyQualifiedMethodNameForMethodWithMultipleParameters() { .containsExactly("com.example.Test", "method", "int, java.lang.Object"); } - @Test - @SuppressWarnings("deprecation") - void getOutermostInstancePreconditions() { - // @formatter:off - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(null, null)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(null, Object.class)); - assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.getOutermostInstance(new Object(), null)); - // @formatter:on - } - - @Test - @SuppressWarnings("deprecation") - void getOutermostInstance() { - var firstClass = new FirstClass(); - var secondClass = firstClass.new SecondClass(); - var thirdClass = secondClass.new ThirdClass(); - - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.SecondClass.ThirdClass.class))// - .contains(thirdClass); - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.SecondClass.class))// - .contains(secondClass); - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, FirstClass.class)).contains(firstClass); - assertThat(ReflectionUtils.getOutermostInstance(thirdClass, String.class)).isEmpty(); - } - @Test void getAllClasspathRootDirectories(@TempDir Path tempDirectory) throws Exception { var root1 = tempDirectory.resolve("root1").toAbsolutePath(); @@ -838,8 +818,7 @@ private void assertNestedCycle(Class start, Class from, Class to) { * @since 1.3 */ @Test - @TrackLogRecords - void findNestedClassesWithInvalidNestedClassFile(LogRecordListener listener) throws Exception { + void findNestedClassesWithInvalidNestedClassFile(@TrackLogRecords LogRecordListener listener) throws Exception { var jarUrl = getClass().getResource("/gh-1436-invalid-nested-class-file.jar"); try (var classLoader = new URLClassLoader(new URL[] { jarUrl })) { @@ -1345,6 +1324,28 @@ void findMethodsIgnoresBridgeMethods() throws Exception { assertEquals(0, methods.stream().filter(Method::isBridge).count()); } + /** + * @see https://github.com/junit-team/junit5/issues/3498 + */ + @Test + void findMethodsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String BEFORE = "before"; + Class superclass = SuperclassWithStaticPackagePrivateBeforeMethod.class; + Method staticMethod = superclass.getDeclaredMethod(BEFORE); + Class subclass = SubclassWithNonStaticPackagePrivateBeforeMethod.class; + Method nonStaticMethod = subclass.getDeclaredMethod(BEFORE); + + // Prerequisite + var methods = findMethods(superclass, ReflectionUtils::isStatic); + assertThat(methods).containsExactly(staticMethod); + + // Actual use cases for this test + methods = findMethods(subclass, ReflectionUtils::isStatic); + assertThat(methods).containsExactly(staticMethod); + methods = findMethods(subclass, ReflectionUtils::isNotStatic); + assertThat(methods).containsExactly(nonStaticMethod); + } + @Test void isGeneric() { for (var method : Generic.class.getMethods()) { @@ -1355,6 +1356,28 @@ void isGeneric() { } } + /** + * @see https://github.com/junit-team/junit5/issues/3532 + */ + @Test + void findFieldsAppliesPredicateBeforeSearchingTypeHierarchy() throws Exception { + final String TEMP_DIR = "tempDir"; + Class superclass = SuperclassWithStaticPackagePrivateTempDirField.class; + Field staticField = superclass.getDeclaredField(TEMP_DIR); + Class subclass = SubclassWithNonStaticPackagePrivateTempDirField.class; + Field nonStaticField = subclass.getDeclaredField(TEMP_DIR); + + // Prerequisite + var fields = findFields(superclass, ReflectionUtils::isStatic, TOP_DOWN); + assertThat(fields).containsExactly(staticField); + + // Actual use cases for this test + fields = findFields(subclass, ReflectionUtils::isStatic, TOP_DOWN); + assertThat(fields).containsExactly(staticField); + fields = findFields(subclass, ReflectionUtils::isNotStatic, TOP_DOWN); + assertThat(fields).containsExactly(nonStaticField); + } + @Test void readFieldValuesPreconditions() { assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.readFieldValues(null, new Object())); @@ -1364,7 +1387,7 @@ void readFieldValuesPreconditions() { @Test void readFieldValuesFromInstance() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, f -> true, TOP_DOWN); + var fields = findFields(ClassWithFields.class, f -> true, TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields()); @@ -1373,7 +1396,7 @@ void readFieldValuesFromInstance() { @Test void readFieldValuesFromClass() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); + var fields = findFields(ClassWithFields.class, ReflectionUtils::isStatic, TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null); @@ -1382,7 +1405,7 @@ void readFieldValuesFromClass() { @Test void readFieldValuesFromInstanceWithTypeFilterForString() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(String.class), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(String.class)); @@ -1391,8 +1414,7 @@ void readFieldValuesFromInstanceWithTypeFilterForString() { @Test void readFieldValuesFromClassWithTypeFilterForString() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), - TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(String.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null, isA(String.class)); @@ -1401,7 +1423,7 @@ void readFieldValuesFromClassWithTypeFilterForString() { @Test void readFieldValuesFromInstanceWithTypeFilterForInteger() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(int.class), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(int.class)); @@ -1410,8 +1432,7 @@ void readFieldValuesFromInstanceWithTypeFilterForInteger() { @Test void readFieldValuesFromClassWithTypeFilterForInteger() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, - isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(Integer.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null, isA(Integer.class)); @@ -1420,7 +1441,7 @@ void readFieldValuesFromClassWithTypeFilterForInteger() { @Test void readFieldValuesFromInstanceWithTypeFilterForDouble() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(double.class), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, new ClassWithFields(), isA(double.class)); @@ -1429,8 +1450,7 @@ void readFieldValuesFromInstanceWithTypeFilterForDouble() { @Test void readFieldValuesFromClassWithTypeFilterForDouble() { - var fields = ReflectionUtils.findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), - TOP_DOWN); + var fields = findFields(ClassWithFields.class, isA(Double.class).and(ReflectionUtils::isStatic), TOP_DOWN); var values = ReflectionUtils.readFieldValues(fields, null, isA(Double.class)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java new file mode 100644 index 000000000000..d6b94d9d2076 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.pkg1; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mimics {@code @TempDir}. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ClassLevelDir { +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java new file mode 100644 index 000000000000..bfa4e4ad8b95 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.pkg1; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Mimics {@code @TempDir}. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface InstanceLevelDir { +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java new file mode 100644 index 000000000000..2895f2b4980d --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.pkg1; + +import org.junit.jupiter.api.BeforeAll; + +/** + * @see https://github.com/junit-team/junit5/issues/3498 + */ +public class SuperclassWithStaticPackagePrivateBeforeMethod { + + @BeforeAll + static void before() { + // no-op + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java new file mode 100644 index 000000000000..4e2bbe7ec696 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java @@ -0,0 +1,23 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.pkg1; + +import java.nio.file.Path; + +/** + * @see https://github.com/junit-team/junit5/issues/3532 + */ +public class SuperclassWithStaticPackagePrivateTempDirField { + + @ClassLevelDir + static Path tempDir; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java new file mode 100644 index 000000000000..c7c6d1e0ac22 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.pkg1.subpkg; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateBeforeMethod; + +/** + * @see https://github.com/junit-team/junit5/issues/3498 + */ +public class SubclassWithNonStaticPackagePrivateBeforeMethod extends SuperclassWithStaticPackagePrivateBeforeMethod { + + @BeforeEach + void before() { + // no-op + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java new file mode 100644 index 000000000000..d7eb33f6a326 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util.pkg1.subpkg; + +import java.nio.file.Path; + +import org.junit.platform.commons.util.pkg1.InstanceLevelDir; +import org.junit.platform.commons.util.pkg1.SuperclassWithStaticPackagePrivateTempDirField; + +/** + * @see https://github.com/junit-team/junit5/issues/3532 + */ +public class SubclassWithNonStaticPackagePrivateTempDirField extends SuperclassWithStaticPackagePrivateTempDirField { + + @InstanceLevelDir + Path tempDir; + +} diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index b56c5dd1be42..70e6bd21d536 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; import java.io.PrintWriter; import java.io.StringWriter; @@ -33,27 +34,26 @@ class ConsoleLauncherTests { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayHelp(String command) { - var consoleLauncher = new ConsoleLauncher((__, ___) -> null, printSink, printSink); + var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); var exitCode = consoleLauncher.run(command, "--help").getExitCode(); assertEquals(0, exitCode); - assertThat(stringWriter.toString()).contains("--help"); + assertThat(stringWriter.toString()).contains("--help", "--disable-banner" /* ... */); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayBanner(String command) { - var consoleLauncher = new ConsoleLauncher((__, ___) -> null, printSink, printSink); + var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); consoleLauncher.run(command); - assertThat(stringWriter.toString()).contains( - "Thanks for using JUnit! Support its development at https://junit.org/sponsoring"); + assertThat(stringWriter.toString()).contains("Thanks for using JUnit!"); } @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void disableBanner(String command, int expectedExitCode) { - var consoleLauncher = new ConsoleLauncher((__, ___) -> null, printSink, printSink); + var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); var exitCode = consoleLauncher.run(command, "--disable-banner").getExitCode(); assertEquals(expectedExitCode, exitCode); @@ -81,9 +81,9 @@ void executeWithoutCommandLineOptions(String command, int expectedExitCode) { static Stream commandsWithEmptyOptionExitCodes() { return Stream.of( // - Arguments.of("execute", -1), // - Arguments.of("discover", -1), // - Arguments.of("engines", 0) // + arguments("execute", -1), // + arguments("discover", -1), // + arguments("engines", 0) // ); } diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java index d17f340c481e..35155450f90b 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java @@ -67,6 +67,7 @@ void reportEntriesCanBeAddedConcurrently() throws Exception { assertThat(treeNode.reports).hasSize(NUM_THREADS * ITEMS_PER_THREAD); } + @SuppressWarnings("resource") private void runConcurrently(Runnable action) throws InterruptedException { ExecutorService executor = new ThreadPoolExecutor(NUM_THREADS, NUM_THREADS, 10, SECONDS, new ArrayBlockingQueue<>(NUM_THREADS)); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java index 9a17de1a825b..838fcacb842e 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java @@ -32,8 +32,7 @@ class SameThreadExecutionIntegrationTests { * @see gh-1688 */ @Test - @TrackLogRecords - void threadInterruptedByUserCode(LogRecordListener listener) { + void threadInterruptedByUserCode(@TrackLogRecords LogRecordListener listener) { EngineTestKit.engine("junit-jupiter")// .selectors(selectClass(InterruptedThreadTestCase.class))// .execute()// diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java index 966c8a9f4e65..c71c625ea1c7 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java @@ -171,7 +171,8 @@ void getWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.get(namespace, key, Number.class)); - assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", + assertEquals( + "Object stored under key [42] is not of required type [java.lang.Number], but was [java.lang.String]: enigma", exception.getMessage()); } @@ -221,7 +222,8 @@ void getOrComputeIfAbsentWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.getOrComputeIfAbsent(namespace, key, defaultCreator, String.class)); - assertEquals("Object stored under key [pi] is not of required type [java.lang.String]", + assertEquals( + "Object stored under key [pi] is not of required type [java.lang.String], but was [java.lang.Float]: 3.14", exception.getMessage()); } @@ -264,7 +266,8 @@ void removeWithTypeSafetyAndInvalidRequiredTypeThrowsException() { Exception exception = assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key, Number.class)); - assertEquals("Object stored under key [42] is not of required type [java.lang.Number]", + assertEquals( + "Object stored under key [42] is not of required type [java.lang.Number], but was [java.lang.String]: enigma", exception.getMessage()); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java index 85732038f6de..34aa62947fad 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java @@ -148,8 +148,7 @@ void launcherWillExecuteEnginesHonoringBothIncludeAndExcludeEngineFilters() { } @Test - @TrackLogRecords - void launcherThrowsExceptionWhenNoEngineMatchesIncludeEngineFilter(LogRecordListener log) { + void launcherThrowsExceptionWhenNoEngineMatchesIncludeEngineFilter(@TrackLogRecords LogRecordListener log) { var engine = new DemoHierarchicalTestEngine("first"); TestDescriptor test1 = engine.addTest("test1", noOp); LauncherDiscoveryRequest request = request() // @@ -169,8 +168,7 @@ void launcherThrowsExceptionWhenNoEngineMatchesIncludeEngineFilter(LogRecordList } @Test - @TrackLogRecords - void launcherWillLogWarningWhenAllEnginesWereExcluded(LogRecordListener log) { + void launcherWillLogWarningWhenAllEnginesWereExcluded(@TrackLogRecords LogRecordListener log) { var engine = new DemoHierarchicalTestEngine("first"); TestDescriptor test = engine.addTest("test1", noOp); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java index f5b777b4b71d..2549fbc4c5e8 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherTests.java @@ -597,8 +597,7 @@ void testPlanThrowsExceptionWhenModified() { } @Test - @TrackLogRecords - void thirdPartyEngineUsingReservedEngineIdPrefixEmitsWarning(LogRecordListener listener) { + void thirdPartyEngineUsingReservedEngineIdPrefixEmitsWarning(@TrackLogRecords LogRecordListener listener) { var id = "junit-using-reserved-prefix"; var launcher = createLauncher(new TestEngineStub(id)); launcher.discover(request().build()); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java index 99398dbc31e8..e0abc5fd55e5 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java @@ -13,6 +13,7 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.JRE.JAVA_22; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -26,6 +27,7 @@ import java.nio.file.Path; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.io.TempDir; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; @@ -41,6 +43,7 @@ * * @since 1.9 */ +@DisabledForJreRange(min = JAVA_22, disabledReason = "https://github.com/junit-team/junit5/issues/3594") public class OpenTestReportGeneratingListenerTests { @TempDir(cleanup = ON_SUCCESS) diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index 5c0c9b44e4ef..cb3eb72ae947 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -3,6 +3,7 @@ exports org.junit.platform.commons exports org.junit.platform.commons.annotation exports org.junit.platform.commons.function exports org.junit.platform.commons.support +exports org.junit.platform.commons.support.conversion requires java.base mandated requires java.logging requires java.management diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt index 1461169b0fee..41bb2afac024 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-launcher.expected.txt @@ -10,6 +10,7 @@ requires org.junit.platform.commons transitive requires org.junit.platform.engine transitive uses org.junit.platform.engine.TestEngine uses org.junit.platform.launcher.LauncherDiscoveryListener +uses org.junit.platform.launcher.LauncherInterceptor uses org.junit.platform.launcher.LauncherSessionListener uses org.junit.platform.launcher.PostDiscoveryFilter uses org.junit.platform.launcher.TestExecutionListener diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java index 53b5742aa4ee..0a2ab12f778a 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -21,7 +21,7 @@ import de.sormuras.bartholdy.tool.GradleWrapper; -import com.gradle.enterprise.testing.annotations.LocalOnly; +import com.gradle.develocity.testing.annotations.LocalOnly; import org.junit.jupiter.api.Test; diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index eb8eb20254f2..a80071de8d41 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import de.sormuras.bartholdy.Result; import de.sormuras.bartholdy.jdk.Jar; @@ -66,10 +67,10 @@ void listAllObservableEngines() { .setTool(new Java()) // .setProject("standalone") // .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("engines", "--disable-banner").build() // + .addArguments("engines", "--disable-ansi-colors", "--disable-banner").build() // .run(false); - assertEquals(0, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); var jupiterVersion = Helper.version("junit-jupiter-engine"); var suiteVersion = Helper.version("junit-platform-suite-engine"); @@ -89,6 +90,8 @@ void compile() throws Exception { var result = Request.builder() // .setTool(new Javac()) // .setProject("standalone") // + .addArguments("-Xlint:-options") // + .addArguments("--release", "8") // .addArguments("-proc:none") // .addArguments("-d", workspace.resolve("bin")) // .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone")) // @@ -98,7 +101,7 @@ void compile() throws Exception { .addArguments(workspace.resolve("src/standalone/VintageIntegration.java")).build() // .run(); - assertEquals(0, result.getExitCode(), result.getOutput("out") + result.getOutput("err")); + assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); assertTrue(result.getOutput("out").isEmpty()); assertTrue(result.getOutput("err").isEmpty()); @@ -327,7 +330,7 @@ private static Result discover(String... args) { .build() // .run(false); - assertEquals(0, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); return result; } @@ -349,13 +352,13 @@ void execute() throws IOException { .addArguments("--classpath", "bin").build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out"), result.getOutput("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err"), result.getOutput("err")); + assertLinesMatch(expectedOutLines, result.getOutputLines("out")); + assertLinesMatch(expectedErrLines, result.getOutputLines("err")); var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); @@ -368,11 +371,12 @@ void execute() throws IOException { @Test @Order(4) void executeOnJava8() throws IOException { + Java java8 = getJava8(); var result = Request.builder() // - .setTool(new Java()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setTool(java8) // + .setJavaHome(java8.getHome()) // .setProject("standalone") // - .addArguments("--show-version") // + .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // @@ -384,11 +388,11 @@ void executeOnJava8() throws IOException { .addArguments("--classpath", "bin").build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + var expectedErrLines = getExpectedErrLinesOnJava8(workspace); assertLinesMatch(expectedOutLines, result.getOutputLines("out")); assertLinesMatch(expectedErrLines, result.getOutputLines("err")); @@ -404,11 +408,12 @@ void executeOnJava8() throws IOException { @Order(5) // https://github.com/junit-team/junit5/issues/2600 void executeOnJava8SelectPackage() throws IOException { + Java java8 = getJava8(); var result = Request.builder() // - .setTool(new Java()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .setTool(java8) // + .setJavaHome(java8.getHome()) // .setProject("standalone") // - .addArguments("--show-version") // + .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // @@ -420,11 +425,11 @@ void executeOnJava8SelectPackage() throws IOException { .addArguments("--classpath", "bin").build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); var workspace = Request.WORKSPACE.resolve("standalone"); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); - var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); + var expectedErrLines = getExpectedErrLinesOnJava8(workspace); assertLinesMatch(expectedOutLines, result.getOutputLines("out")); assertLinesMatch(expectedErrLines, result.getOutputLines("err")); @@ -436,6 +441,13 @@ void executeOnJava8SelectPackage() throws IOException { + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } + private static List getExpectedErrLinesOnJava8(Path workspace) throws IOException { + var expectedErrLines = new ArrayList(); + expectedErrLines.add(">> JAVA VERSION >>"); + expectedErrLines.addAll(Files.readAllLines(workspace.resolve("expected-err.txt"))); + return expectedErrLines; + } + @Test @Order(6) @Disabled("https://github.com/junit-team/junit5/issues/1724") @@ -461,6 +473,29 @@ void executeWithJarredTestClasses() { .build() // .run(false); - assertEquals(1, result.getExitCode(), String.join("\n", result.getOutputLines("out"))); + assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + } + + private static String getExitCodeMessage(Result result) { + return "Exit codes don't match. Stdout:\n" + result.getOutput("out") + // + "\n\nStderr:\n" + result.getOutput("err") + "\n"; + } + + /** + * Special override of class {@link Java} to resolve against a different {@code JAVA_HOME}. + */ + private static Java getJava8() { + Path java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); + return new Java() { + @Override + public Path getHome() { + return java8Home; + } + + @Override + public String getVersion() { + return "8"; + } + }; } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8ae205fbcbeb..25bb18fd4d02 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,18 +25,20 @@ dependencyResolutionManagement { } val buildParameters = the() -val gradleEnterpriseServer = "https://ge.junit.org" +val develocityServer = "https://ge.junit.org" +val useDevelocityInstance = !gradle.startParameter.isBuildScan gradleEnterprise { + if (useDevelocityInstance) { + // Publish to scans.gradle.com when `--scan` is used explicitly + server = develocityServer + } buildScan { capture.isTaskInputFiles = true isUploadInBackground = !buildParameters.ci - publishAlways() - - // Publish to scans.gradle.com when `--scan` is used explicitly - if (!gradle.startParameter.isBuildScan) { - server = gradleEnterpriseServer + if (useDevelocityInstance) { + publishAlways() this as BuildScanExtensionWithHiddenFeatures publishIfAuthenticated() } @@ -50,7 +52,7 @@ gradleEnterprise { } } - if (buildParameters.enterprise.testDistribution.enabled) { + if (buildParameters.develocity.testDistribution.enabled) { tag("test-distribution") } } @@ -60,14 +62,15 @@ buildCache { local { isEnabled = !buildParameters.ci } - remote { - url = uri(buildParameters.buildCache.url.getOrElse("$gradleEnterpriseServer/cache/")) - val buildCacheUsername = buildParameters.buildCache.username.orNull?.ifBlank { null } - val buildCachePassword = buildParameters.buildCache.password.orNull?.ifBlank { null } - isPush = buildParameters.ci && buildCacheUsername != null && buildCachePassword != null - credentials { - username = buildCacheUsername - password = buildCachePassword + if (useDevelocityInstance) { + remote(gradleEnterprise.buildCache) { + server = buildParameters.buildCache.server.orNull + val authenticated = System.getenv("GRADLE_ENTERPRISE_ACCESS_KEY") != null + isPush = buildParameters.ci && authenticated + } + } else { + remote { + url = uri(buildParameters.buildCache.server.getOrElse(develocityServer)).resolve("/cache/") } } }