diff --git a/.github/actions/maven-central-user-token/action.yml b/.github/actions/maven-central-user-token/action.yml new file mode 100644 index 000000000000..37266d5e86a0 --- /dev/null +++ b/.github/actions/maven-central-user-token/action.yml @@ -0,0 +1,17 @@ +name: Prepare Maven Central user token +description: Compute the Maven Central user token from username and password +inputs: + username: + required: true + description: Maven Central username + password: + required: true + description: Maven Central password +runs: + using: "composite" + steps: + - shell: bash + run: | + USER_TOKEN=$(printf "${{ inputs.username }}:${{ inputs.password }}" | base64) + echo "::add-mask::$USER_TOKEN" + echo "MAVEN_CENTRAL_USER_TOKEN=$USER_TOKEN" >> $GITHUB_ENV diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ea024d3d17e6..d256a62d0f55 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,6 +3,7 @@ extends: [ 'github>junit-team/renovate-config', ], + baseBranches: ["main", "/^develop\\/.*/"], packageRules: [ { matchCurrentValue: '/^2\\./', diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 31c9a5baa610..5664eeca4c65 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: - name: Check out repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: languages: ${{ matrix.language }} tools: linked @@ -47,4 +47,4 @@ jobs: -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e41fbe61130b..aadc10b78069 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,7 +41,7 @@ jobs: jacocoRootReport \ --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Upload to Codecov.io - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -85,8 +85,8 @@ jobs: - name: Publish uses: ./.github/actions/run-gradle env: - ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 56874e9f9c9d..c24ae2e85e54 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: results.sarif diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b363033f4b7..6988b6e873b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,15 +6,20 @@ on: releaseVersion: description: Version to be released (e.g. "5.12.0-M1") required: true - stagingRepoId: - description: ID of the Nexus staging repository (e.g. "orgjunit-1159") + deploymentId: + description: ID of the Maven Central Publish Portal deployment required: true + dryRun: + type: boolean + description: Enable dry-run mode + required: false + default: false permissions: read-all env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - STAGING_REPO_URL: https://oss.sonatype.org/service/local/repositories/${{ github.event.inputs.stagingRepoId }}/content + STAGING_REPO_URL: https://central.sonatype.com/api/v1/publisher/deployment/${{ github.event.inputs.deploymentId }}/download RELEASE_TAG: r${{ github.event.inputs.releaseVersion }} jobs: @@ -31,10 +36,16 @@ jobs: with: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" + - name: Prepare Maven Central user token + uses: ./.github/actions/maven-central-user-token + with: + username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - name: Download reference JAR from staging repository id: referenceJar run: | curl --silent --fail --location --output /tmp/reference.jar \ + --header "Authorization: Bearer $MAVEN_CENTRAL_USER_TOKEN" \ "${{ env.STAGING_REPO_URL }}/org/junit/jupiter/junit-jupiter-api/${{ github.event.inputs.releaseVersion }}/junit-jupiter-api-${{ github.event.inputs.releaseVersion }}.jar" sudo apt-get update && sudo apt-get install --yes jc unzip -c /tmp/reference.jar META-INF/MANIFEST.MF | jc --jar-manifest | jq '.[0]' > /tmp/manifest.json @@ -51,6 +62,7 @@ jobs: :verifyArtifactsInStagingRepositoryAreReproducible \ --remote-repo-url=${{ env.STAGING_REPO_URL }} - name: Generate build provenance attestations + if: ${{ inputs.dryRun == false }} uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 with: subject-path: build/repo/**/*.jar @@ -64,12 +76,19 @@ jobs: name: Verify consumability runs-on: ubuntu-latest steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 1 + ref: "refs/tags/${{ env.RELEASE_TAG }}" + path: junit5 - name: Check out samples repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: ${{ github.repository_owner }}/junit5-samples token: ${{ secrets.GH_TOKEN }} fetch-depth: 1 + path: junit5-samples - name: Set up JDK uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: @@ -78,10 +97,20 @@ jobs: - uses: sbt/setup-sbt@26ab4b0fa1c47fa62fc1f6e51823a658fb6c760c # v1.1.7 - name: Update JUnit dependencies in samples run: java src/Updater.java ${{ github.event.inputs.releaseVersion }} + working-directory: junit5-samples + - name: Prepare Maven Central user token + uses: ./junit5/.github/actions/maven-central-user-token + with: + username: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - name: Inject staging repository URL run: java src/StagingRepoInjector.java ${{ env.STAGING_REPO_URL }} + working-directory: junit5-samples - name: Build samples - run: java src/Builder.java + run: java src/Builder.java --exclude=junit5-jupiter-starter-bazel,junit5-jupiter-starter-sbt + working-directory: junit5-samples + env: + MAVEN_ARGS: --settings ${{ github.workspace }}/junit5-samples/src/central-staging-maven-settings.xml --activate-profiles central-staging close_github_milestone: name: Close GitHub milestone @@ -90,6 +119,7 @@ jobs: issues: write steps: - name: Close GitHub milestone + if: ${{ inputs.dryRun == false }} uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: result-encoding: string @@ -116,8 +146,8 @@ jobs: console.log(requestBody); await github.rest.issues.updateMilestone(requestBody); - release_staging_repo: - name: Release staging repo + publish_deployment: + name: Publish to Maven Central needs: [ verify_reproducibility, verify_consumability, close_github_milestone ] runs-on: ubuntu-latest steps: @@ -127,19 +157,20 @@ jobs: fetch-depth: 1 ref: "refs/tags/${{ env.RELEASE_TAG }}" - name: Release staging repository + if: ${{ inputs.dryRun == false }} uses: ./.github/actions/run-gradle env: - ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} - ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + JRELEASER_MAVENCENTRAL_STAGE: PUBLISH + JRELEASER_MAVENCENTRAL_DEPLOYMENT_ID: ${{ inputs.deploymentId }} with: encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - arguments: | - releaseSonatypeStagingRepository \ - --staging-repository-id=${{ github.event.inputs.stagingRepoId }} + arguments: jreleaserDeploy publish_documentation: name: Publish documentation - needs: release_staging_repo + needs: publish_deployment runs-on: ubuntu-latest steps: - name: Check out repository @@ -155,7 +186,18 @@ jobs: run: | git config --global user.name "JUnit Team" git config --global user.email "team@junit.org" - - name: Build and publish documentation + - name: Build documentation + uses: ./.github/actions/run-gradle + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + arguments: | + --no-build-cache \ + --no-configuration-cache \ + clean \ + gitPublishCopy \ + -Pdocumentation.replaceCurrentDocs=${{ contains(github.event.inputs.releaseVersion, '-') && 'false' || 'true' }} + - name: Publish documentation + if: ${{ inputs.dryRun == false }} uses: ./.github/actions/run-gradle env: GIT_USERNAME: git @@ -165,10 +207,10 @@ jobs: arguments: | --no-build-cache \ --no-configuration-cache \ - clean \ gitPublishPush \ -Pdocumentation.replaceCurrentDocs=${{ contains(github.event.inputs.releaseVersion, '-') && 'false' || 'true' }} - name: Wait for deployment to GitHub Pages + if: ${{ inputs.dryRun == false }} id: pagesDeployment timeout-minutes: 20 run: | @@ -176,13 +218,14 @@ jobs: ./.github/scripts/waitForUrl.sh "$URL" echo "pdfUrl=$URL" >> "$GITHUB_OUTPUT" - name: Verify integrity of PDF version of User Guide + if: ${{ inputs.dryRun == false }} run: | curl --silent --fail --location --output /tmp/junit-user-guide.pdf "${{ steps.pagesDeployment.outputs.pdfUrl }}" pdfinfo /tmp/junit-user-guide.pdf wait_for_maven_central: name: Wait for Maven Central - needs: release_staging_repo + needs: publish_deployment runs-on: ubuntu-latest steps: - name: Check out repository @@ -196,6 +239,7 @@ jobs: name: local-maven-repository path: build/repo - name: Wait for sync to Maven Central + if: ${{ inputs.dryRun == false }} timeout-minutes: 30 run: | find build/repo -name '*.pom' -printf './.github/scripts/waitForMavenCentralSync.sh %P\n' | sh @@ -220,6 +264,7 @@ jobs: - name: Update JUnit dependencies in samples run: java src/Updater.java ${{ github.event.inputs.releaseVersion }} - name: Build samples + if: ${{ inputs.dryRun == false }} run: java src/Builder.java - name: Create release branch run: | @@ -228,9 +273,12 @@ jobs: git switch -c "${{ env.RELEASE_TAG }}" git status git commit -a -m "Use ${{ github.event.inputs.releaseVersion }}" + - name: Push release branch + if: ${{ inputs.dryRun == false }} + run: | git push origin "${{ env.RELEASE_TAG }}" - name: Update main branch (only for GA releases) - if: ${{ !contains(github.event.inputs.releaseVersion, '-') }} + if: ${{ inputs.dryRun == false && !contains(github.event.inputs.releaseVersion, '-') }} run: | git switch main git merge --ff-only "${{ env.RELEASE_TAG }}" @@ -238,6 +286,7 @@ jobs: create_github_release: name: Create GitHub release + if: ${{ inputs.dryRun == false }} needs: wait_for_maven_central runs-on: ubuntu-latest permissions: diff --git a/README.md b/README.md index 7268c5826da6..20f691ec93f5 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ This repository is the home of _JUnit 5_. [![Support JUnit](https://img.shields.io/badge/%F0%9F%92%9A-Support%20JUnit-brightgreen.svg)](https://junit.org/sponsoring) -* **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo) +* **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo), [Netflix](https://www.netflix.com/) * **Silver Sponsors:** [Micromata](https://www.micromata.de), [Quo Card](https://quo-digital.jp) -* **Bronze Sponsors:** [Premium Minds](https://www.premium-minds.com), [Testmo](https://www.testmo.com), [codefortynine](https://codefortynine.com), [Info Support](https://www.infosupport.com), [Stiltsoft](https://stiltsoft.com), [Code Intelligence](https://www.code-intelligence.com), [Route4Me](https://route4me.com/), [Testiny](https://www.testiny.io/) +* **Bronze Sponsors:** [Premium Minds](https://www.premium-minds.com), [codefortynine](https://codefortynine.com), [Info Support](https://www.infosupport.com), [Code Intelligence](https://www.code-intelligence.com), [Route4Me](https://route4me.com/), [Testiny](https://www.testiny.io/) ## Latest Releases - General Availability (GA): [JUnit 5.12.2](https://github.com/junit-team/junit5/releases/tag/r5.12.2) (April 11, 2025) -- Preview (Milestone/Release Candidate): [JUnit 5.13.0-M3](https://github.com/junit-team/junit5/releases/tag/r5.13.0-M3) (May 2, 2025) +- Preview (Milestone/Release Candidate): [JUnit 5.13.0-RC1](https://github.com/junit-team/junit5/releases/tag/r5.13.0-RC1) (May 16, 2025) ## Documentation @@ -94,7 +94,7 @@ Consult the [Dependency Metadata] section of the [User Guide] for a list of all of the JUnit Platform, JUnit Jupiter, and JUnit Vintage. See also for releases and - for snapshots. + for snapshots. [Codecov]: https://codecov.io/gh/junit-team/junit5 diff --git a/RELEASING.md b/RELEASING.md index bdbb753eba27..4616ac429047 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,11 +7,11 @@ - [ ] Change release date in Release Notes - [ ] Change release date in `README.MD` - [ ] Commit with message "Release ${VERSION}" -- [ ] Execute `./gradlew --no-build-cache --no-configuration-cache clean build publish closeSonatypeStagingRepository` +- [ ] Execute `./gradlew --no-build-cache --no-configuration-cache clean build jreleaserDeploy` - [ ] Tag current commit: `git tag -s -m ${VERSION} r${VERSION}` - [ ] Change `version`, `platformVersion`, and `vintageVersion` properties in `gradle.properties` on release branch to new development versions and commit with message "Back to snapshots for further development" or similar - [ ] Push release branch and tag to GitHub: `git push --set-upstream --follow-tags origin HEAD` -- [ ] Trigger a [release build](https://github.com/junit-team/junit5/actions/workflows/release.yml): `gh workflow run --ref r${VERSION} -f releaseVersion=${VERSION} -f stagingRepoId=orgjunit-1234 release.yml` +- [ ] Trigger a [release build](https://github.com/junit-team/junit5/actions/workflows/release.yml): `gh workflow run --ref r${VERSION} -f releaseVersion=${VERSION} -f deploymentId=${DEPLOYMENT_ID} release.yml` - Select the release branch - Enter the version to be released - Enter the staging repository ID from the output of above Gradle build diff --git a/build.gradle.kts b/build.gradle.kts index cd79a120700b..5f40357a56cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,12 @@ +import junitbuild.extensions.dependencyProject + plugins { - alias(libs.plugins.nexusPublish) id("junitbuild.base-conventions") id("junitbuild.build-metadata") id("junitbuild.checkstyle-nohttp") id("junitbuild.dependency-update-check") id("junitbuild.jacoco-aggregation-conventions") + id("junitbuild.maven-central-publishing") id("junitbuild.temp-maven-repo") } @@ -55,10 +57,3 @@ dependencies { jacocoAggregation(projects.jupiterTests) jacocoAggregation(projects.platformTests) } - -nexusPublishing { - packageGroup = "org.junit" - repositories { - sonatype() - } -} diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 799c16ae3833..a5f806e99b60 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -2,10 +2,14 @@ import junitbuild.exec.CaptureJavaExecOutput import junitbuild.exec.ClasspathSystemPropertyProvider import junitbuild.exec.GenerateStandaloneConsoleLauncherShadowedArtifactsFile import junitbuild.exec.RunConsoleLauncher +import junitbuild.extensions.dependencyProject +import junitbuild.extensions.isSnapshot +import junitbuild.extensions.javaModuleName import junitbuild.javadoc.ModuleSpecificJavadocFileOption import org.asciidoctor.gradle.base.AsciidoctorAttributeProvider import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.ysb33r.grolifant.api.core.jvm.ExecutionMode.JAVA_EXEC plugins { alias(libs.plugins.asciidoctorConvert) @@ -247,7 +251,8 @@ tasks { val generateApiTables by registering(JavaExec::class) { classpath = tools.runtimeClasspath mainClass = "org.junit.api.tools.ApiReportGenerator" - jvmArgumentProviders += ClasspathSystemPropertyProvider("api.classpath", apiReportClasspath.get()) + systemProperty("api.moduleNames", modularProjects.map { it.javaModuleName }.sorted().joinToString(",")) + jvmArgumentProviders += ClasspathSystemPropertyProvider("api.modulePath", apiReportClasspath.get()) argumentProviders += CommandLineArgumentProvider { listOf( "DEPRECATED=${deprecatedApisTableFile.get().asFile.absolutePath}", @@ -377,6 +382,7 @@ tasks { } asciidoctorPdf { + setExecutionMode(JAVA_EXEC) // Avoid classpath conflicts with other Gradle plugins (e.g. JReleaser) sources { include("user-guide/index.adoc") } @@ -424,6 +430,7 @@ tasks { this as StandardJavadocDocletOptions splitIndex(true) addBooleanOption("Xdoclint:all,-missing", true) + addBooleanOption("Werror", true) addBooleanOption("html5", true) addMultilineStringsOption("tag").value = listOf( "apiNote:a:API Note:", diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index fbfc2021e938..2342763e5680 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -3,7 +3,7 @@ ifdef::backend-pdf[] :javadoc-root: https://junit.org/junit5/docs/{docs-version}/api endif::[] // Snapshot Repository -:snapshot-repo: https://oss.sonatype.org/content/repositories/snapshots +:snapshot-repo: https://central.sonatype.com/repository/maven-snapshots // Base Links :junit-team: https://github.com/junit-team :junit5-repo: {junit-team}/junit5 diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index af8a4feabdaf..33d93ad199b4 100644 --- a/documentation/src/docs/asciidoc/release-notes/index.adoc +++ b/documentation/src/docs/asciidoc/release-notes/index.adoc @@ -9,7 +9,7 @@ Stefan Bechtold; Sam Brannen; Johannes Link; Matthias Merdes; Marc Philipp; Juli :last-update-label!: // -This document contains the _change log_ for all JUnit 5 releases since 5.11 GA. +This document contains the _change log_ for all JUnit 5 releases since 5.12 GA. Please refer to the <<../user-guide/index.adoc#user-guide,User Guide>> for comprehensive reference documentation for programmers writing tests, extension authors, and engine @@ -17,6 +17,8 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] +include::{basedir}/release-notes-5.13.0-RC1.adoc[] + include::{basedir}/release-notes-5.13.0-M3.adoc[] include::{basedir}/release-notes-5.13.0-M2.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc new file mode 100644 index 000000000000..2cac539083b9 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.13.0-RC1.adoc @@ -0,0 +1,37 @@ +[[release-notes-5.13.0-RC1]] +== 5.13.0-RC1 + +*Date of Release:* May 16, 2025 + +*Scope:* Minor bug fixes and enhancements since 5.13.0-M1 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/96?closed=1+[5.13.0-RC1] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-5.13.0-RC1-junit-platform]] +=== JUnit Platform + +No changes. + + +[[release-notes-5.13.0-RC1-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.13.0-RC1-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* Validate _all_ versions specified in `@EnabledOnJre` and `@DisabledOnJre` annotations. + +[[release-notes-5.13.0-RC1-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* Add support for Kotlin `Sequence` to `@MethodSource`, `@FieldSource`, and + `@TestFactory`. + + +[[release-notes-5.13.0-RC1-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index e84ed85c59be..b9c86dbb8a89 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1949,10 +1949,11 @@ of the annotated `@ParameterizedClass` or `@ParameterizedTest`. Generally speaki translates to a `Stream` of `Arguments` (i.e., `Stream`); however, the actual concrete return type can take on many forms. In this context, a "stream" is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, `DoubleStream`, -`LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects, or -an array of primitives. The "arguments" within the stream can be supplied as an instance -of `Arguments`, an array of objects (e.g., `Object[]`), or a single value if the -parameterized class or test method accepts a single argument. +`LongStream`, `IntStream`, `Collection`, `Iterator`, `Iterable`, an array of objects or +primitives, or any type that provides an `iterator(): Iterator` method (such as, for +example, a `kotlin.sequences.Sequence`). The "arguments" within the stream can be supplied +as an instance of `Arguments`, an array of objects (e.g., `Object[]`), or a single value +if the parameterized class or test method accepts a single argument. If the return type is `Stream` or one of the primitive streams, JUnit will properly close it by calling `BaseStream.close()`, @@ -2038,10 +2039,11 @@ In this context, a "stream" is anything that JUnit can reliably convert to a `St however, the actual concrete field type can take on many forms. Generally speaking this translates to a `Collection`, an `Iterable`, a `Supplier` of a stream (`Stream`, `DoubleStream`, `LongStream`, or `IntStream`), a `Supplier` of an `Iterator`, an array of -objects, or an array of primitives. Each set of "arguments" within the "stream" can be -supplied as an instance of `Arguments`, an array of objects (for example, `Object[]`, -`String[]`, etc.), or a single value if the parameterized class or test method accepts a -single argument. +objects or primitives, or any type that provides an `iterator(): Iterator` method (such +as, for example, a `kotlin.sequences.Sequence`). Each set of "arguments" within the +"stream" can be supplied as an instance of `Arguments`, an array of objects (for example, +`Object[]`, `String[]`, etc.), or a single value if the parameterized class ortest method accepts +a single argument. [WARNING] ==== @@ -2870,7 +2872,11 @@ generated at runtime by a factory method that is annotated with `@TestFactory`. In contrast to `@Test` methods, a `@TestFactory` method is not itself a test case but rather a factory for test cases. Thus, a dynamic test is the product of a factory. Technically speaking, a `@TestFactory` method must return a single `DynamicNode` or a -`Stream`, `Collection`, `Iterable`, `Iterator`, or array of `DynamicNode` instances. +_stream_ of `DynamicNode` instances or any of its subclasses. In this context, a "stream" +is anything that JUnit can reliably convert into a `Stream`, such as `Stream`, +`Collection`, `Iterator`, `Iterable`, an array of objects, or any type that provides an +`iterator(): Iterator` method (such as, for example, a `kotlin.sequences.Sequence`). + Instantiable subclasses of `DynamicNode` are `DynamicContainer` and `DynamicTest`. `DynamicContainer` instances are composed of a _display name_ and a list of dynamic child nodes, enabling the creation of arbitrarily nested hierarchies of dynamic nodes. diff --git a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java index 01e6b7832988..1cf2308ed574 100644 --- a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java +++ b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java @@ -12,6 +12,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toUnmodifiableSet; import java.io.BufferedOutputStream; import java.io.File; @@ -28,7 +29,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Stream; @@ -141,12 +141,14 @@ private static ScanResult scanClasspath() { .enableClassInfo() // .enableMethodInfo() // .enableAnnotationInfo(); // - var apiClasspath = System.getProperty("api.classpath"); - if (apiClasspath != null) { + var apiClasspath = System.getProperty("api.modulePath"); + var apiModules = System.getProperty("api.moduleNames"); + if (apiClasspath != null && apiModules != null) { var paths = Arrays.stream(apiClasspath.split(File.pathSeparator)).map(Path::of).toArray(Path[]::new); var bootLayer = ModuleLayer.boot(); + var roots = Arrays.stream(apiModules.split(",")).collect(toUnmodifiableSet()); var configuration = bootLayer.configuration().resolveAndBind(ModuleFinder.of(), ModuleFinder.of(paths), - Set.of()); + roots); var layer = bootLayer.defineModulesWithOneLoader(configuration, ClassLoader.getPlatformClassLoader()); classGraph = classGraph.overrideModuleLayers(layer); } diff --git a/gradle.properties b/gradle.properties index 1921177d772d..6a96f24ce567 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.13.0-M3 +version = 5.13.0-RC1 jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.13.0-M3 +platformVersion = 1.13.0-RC1 vintageGroup = org.junit.vintage -vintageVersion = 5.13.0-M3 +vintageVersion = 5.13.0-RC1 # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError diff --git a/gradle/base/code-generator-model/build.gradle.kts b/gradle/base/code-generator-model/build.gradle.kts index aa3ba93c438a..bc0172f0f072 100644 --- a/gradle/base/code-generator-model/build.gradle.kts +++ b/gradle/base/code-generator-model/build.gradle.kts @@ -1,9 +1,3 @@ plugins { `kotlin-dsl` } - -group = "junitbuild.base" - -repositories { - gradlePluginPortal() -} diff --git a/gradle/base/dsl-extensions/build.gradle.kts b/gradle/base/dsl-extensions/build.gradle.kts new file mode 100644 index 000000000000..bc0172f0f072 --- /dev/null +++ b/gradle/base/dsl-extensions/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + `kotlin-dsl` +} diff --git a/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt new file mode 100644 index 000000000000..ad04bf5f33ab --- /dev/null +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/DependencyExtensions.kt @@ -0,0 +1,8 @@ +package junitbuild.extensions + +import org.gradle.api.provider.Provider +import org.gradle.plugin.use.PluginDependency + +// see https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers +val Provider.markerCoordinates: Provider + get() = map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" } diff --git a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt similarity index 96% rename from gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt index 33c7a591b546..8dc61fa656de 100644 --- a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/ProjectExtensions.kt @@ -1,3 +1,5 @@ +package junitbuild.extensions + import org.gradle.api.Project import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.artifacts.VersionCatalog diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/extensions/Extensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt similarity index 100% rename from gradle/plugins/common/src/main/kotlin/junitbuild/extensions/Extensions.kt rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/StringExtensions.kt diff --git a/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt similarity index 85% rename from gradle/plugins/common/src/main/kotlin/TaskExtensions.kt rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt index 7ad4e7ab46cb..9cd596dfbfda 100644 --- a/gradle/plugins/common/src/main/kotlin/TaskExtensions.kt +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/TaskExtensions.kt @@ -1,3 +1,5 @@ +package junitbuild.extensions + import org.gradle.api.Task import org.gradle.internal.os.OperatingSystem diff --git a/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt similarity index 67% rename from gradle/plugins/common/src/main/kotlin/VersionExtensions.kt rename to gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt index 8d106218b896..0991e5d1c3fd 100644 --- a/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/VersionExtensions.kt @@ -1 +1,3 @@ +package junitbuild.extensions + fun Any.isSnapshot(): Boolean = toString().contains("SNAPSHOT") diff --git a/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts new file mode 100644 index 000000000000..11b39c463b79 --- /dev/null +++ b/gradle/base/dsl-extensions/src/main/kotlin/junitbuild/extensions/junitbuild.dsl-extensions.gradle.kts @@ -0,0 +1 @@ +// Just a dummy plugin to get the extensions on the classpath of downstream builds diff --git a/gradle/base/gradle.properties b/gradle/base/gradle.properties new file mode 100644 index 000000000000..de911ccbf7bf --- /dev/null +++ b/gradle/base/gradle.properties @@ -0,0 +1 @@ +group = junitbuild.base diff --git a/gradle/base/settings.gradle.kts b/gradle/base/settings.gradle.kts index 66f6697452eb..67d0cdacc360 100644 --- a/gradle/base/settings.gradle.kts +++ b/gradle/base/settings.gradle.kts @@ -1,3 +1,10 @@ rootProject.name = "base" +dependencyResolutionManagement { + repositories { + gradlePluginPortal() + } +} + include("code-generator-model") +include("dsl-extensions") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95679e033664..6ab7d672fb00 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } # check whether the Java condition in platform-tooling-support-tests.gradle.kts can be changed when updating -archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.0" } +archunit = { module = "com.tngtech.archunit:archunit-junit5", version = "1.4.1" } assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } @@ -98,10 +98,10 @@ develocity = { id = "com.gradle.develocity", version = "4.0.1" } foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.10.0" } gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.1" } jmh = { id = "me.champeau.jmh", version = "0.7.3" } +jreleaser = { id = "org.jreleaser", version = "1.18.0" } # check if workaround in gradle.properties can be removed when updating -kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.20" } -nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } +kotlin = { id = "org.jetbrains.kotlin.jvm", version = "2.1.21" } plantuml = { id = "io.freefair.plantuml", version = "8.13.1" } shadow = { id = "com.gradleup.shadow", version = "8.3.6" } -spotless = { id = "com.diffplug.spotless", version = "6.25.0" } +spotless = { id = "com.diffplug.spotless", version = "7.0.3" } versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } diff --git a/gradle/plugins/code-generator/build.gradle.kts b/gradle/plugins/code-generator/build.gradle.kts index e9f2ef657e47..fda16a028335 100644 --- a/gradle/plugins/code-generator/build.gradle.kts +++ b/gradle/plugins/code-generator/build.gradle.kts @@ -4,6 +4,7 @@ plugins { dependencies { implementation("junitbuild.base:code-generator-model") + implementation("junitbuild.base:dsl-extensions") implementation(projects.common) implementation(libs.jackson.dataformat.yaml) implementation(libs.jackson.module.kotlin) diff --git a/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts b/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts index 59d9fa36ff43..1846eb6ae728 100644 --- a/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts +++ b/gradle/plugins/code-generator/src/main/kotlin/junitbuild.code-generator.gradle.kts @@ -1,3 +1,4 @@ +import junitbuild.extensions.dependencyFromLibs import junitbuild.generator.GenerateJreRelatedSourceCode plugins { diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts index 48c05c7f0a80..e15665d0d6e0 100644 --- a/gradle/plugins/common/build.gradle.kts +++ b/gradle/plugins/common/build.gradle.kts @@ -1,8 +1,11 @@ +import junitbuild.extensions.markerCoordinates + plugins { `kotlin-dsl` } dependencies { + implementation("junitbuild.base:dsl-extensions") implementation(projects.buildParameters) implementation(libs.plugins.kotlin.markerCoordinates) implementation(libs.plugins.bnd.markerCoordinates) @@ -10,11 +13,8 @@ dependencies { implementation(libs.plugins.develocity.markerCoordinates) implementation(libs.plugins.foojayResolver.markerCoordinates) implementation(libs.plugins.jmh.markerCoordinates) + implementation(libs.plugins.jreleaser.markerCoordinates) implementation(libs.plugins.shadow.markerCoordinates) implementation(libs.plugins.spotless.markerCoordinates) implementation(libs.plugins.versions.markerCoordinates) } - -// see https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers -val Provider.markerCoordinates: Provider - get() = map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts index ae6f47ef34d7..f207138111fe 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.requiredVersionFromLibs + plugins { base checkstyle diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts index 641002f94ad2..0bcb0ce67641 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts @@ -1,3 +1,6 @@ +import junitbuild.extensions.dependencyFromLibs +import junitbuild.extensions.requiredVersionFromLibs + plugins { id("junitbuild.checkstyle-conventions") } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts index ef29df71f8c0..eb2965a09a5a 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jacoco-conventions.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.requiredVersionFromLibs + plugins { jacoco id("junitbuild.build-parameters") 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 b14973e05474..becfe893a62a 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-library-conventions.gradle.kts @@ -1,4 +1,6 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import junitbuild.extensions.javaModuleName +import junitbuild.extensions.isSnapshot import junitbuild.java.ModuleCompileOptions import junitbuild.java.ModulePathArgumentProvider import junitbuild.java.PatchModuleArgumentProvider @@ -274,8 +276,7 @@ tasks.compileJava { tasks.compileTestJava { // See: https://docs.oracle.com/en/java/javase/12/tools/javac.html options.compilerArgs.addAll(listOf( - "-Xlint", // Enables all recommended warnings. - "-Xlint:-overrides", // Disables "method overrides" warnings. + "-Xlint:all", // Enables all recommended warnings. "-Werror", // Terminates compilation when warnings occur. "-parameters" // Generates metadata for reflection on method parameters. )) diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts index a1d529652650..377a49fb9085 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.jmh-conventions.gradle.kts @@ -1,3 +1,6 @@ +import junitbuild.extensions.requiredVersionFromLibs +import junitbuild.extensions.dependencyFromLibs + plugins { id("me.champeau.jmh") } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts index 64b1074d345d..1366ce3159dc 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.junit4-compatibility.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.dependencyFromLibs + plugins { `java-library` } 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 90146181e380..08664ee9a0d0 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 @@ -1,3 +1,5 @@ +import junitbuild.extensions.isSnapshot + plugins { `maven-publish` signing diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts index 12a1d64470b0..ee94535fab22 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.spotless-conventions.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.requiredVersionFromLibs + plugins { id("com.diffplug.spotless") } @@ -9,7 +11,7 @@ spotless { format("misc") { target("*.gradle.kts", "gradle/plugins/**/*.gradle.kts", "*.gitignore") targetExclude("gradle/plugins/**/build/**") - indentWithTabs() + leadingSpacesToTabs() trimTrailingWhitespace() endWithNewline() } 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 b5f63a5906a5..b79be1dbbeed 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 @@ -1,6 +1,8 @@ - import com.gradle.develocity.agent.gradle.internal.test.PredictiveTestSelectionConfigurationInternal import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionMode +import junitbuild.extensions.trackOperationSystemAsInput +import junitbuild.extensions.dependencyFromLibs +import junitbuild.extensions.bundleFromLibs import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL import org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt index 896e76090c6f..4ab463b11eba 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt @@ -20,7 +20,7 @@ import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.the import org.gradle.process.CommandLineArgumentProvider import org.gradle.process.ExecOperations -import trackOperationSystemAsInput +import junitbuild.extensions.trackOperationSystemAsInput import java.io.ByteArrayOutputStream import javax.inject.Inject diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt index 8149094e6128..6b21c5a1bb7b 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/PatchModuleArgumentProvider.kt @@ -1,6 +1,6 @@ package junitbuild.java -import javaModuleName +import junitbuild.extensions.javaModuleName import org.gradle.api.Named import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection diff --git a/gradle/plugins/publishing/build.gradle.kts b/gradle/plugins/publishing/build.gradle.kts new file mode 100644 index 000000000000..e010a00708d4 --- /dev/null +++ b/gradle/plugins/publishing/build.gradle.kts @@ -0,0 +1,10 @@ +import junitbuild.extensions.markerCoordinates + +plugins { + `kotlin-dsl` +} + +dependencies { + implementation("junitbuild.base:dsl-extensions") + implementation(libs.plugins.jreleaser.markerCoordinates) +} diff --git a/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts new file mode 100644 index 000000000000..0ebec70fa49b --- /dev/null +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild.maven-central-publishing.gradle.kts @@ -0,0 +1,72 @@ +import org.jreleaser.model.Active.RELEASE +import org.jreleaser.model.api.deploy.maven.MavenCentralMavenDeployer.Stage +import java.util.Properties + +plugins { + id("org.jreleaser") + id("junitbuild.temp-maven-repo") +} + +val tempRepoDir: File by extra + +tasks.jreleaserDeploy { + dependsOn("publishAllSubprojectsToTempRepository") + outputs.upToDateWhen { false } + doLast { + val outputProperties = Properties() + layout.buildDirectory.file("jreleaser/output.properties").get().asFile.inputStream().use { input -> + outputProperties.load(input) + } + val deploymentId = outputProperties.getProperty("deploymentId") + if (deploymentId != null) { + println("Deployment ID: $deploymentId") + println("Staging Repo URL: https://central.sonatype.com/api/v1/publisher/deployment/$deploymentId/download") + } + } +} + +val mavenCentralUsername = providers.gradleProperty("mavenCentralUsername") +val mavenCentralPassword = providers.gradleProperty("mavenCentralPassword") + +jreleaser { + deploy { + maven { + mavenCentral { + register("artifacts") { + active = RELEASE + url = "https://central.sonatype.com/api/v1/publisher" + username = mavenCentralUsername + password = mavenCentralPassword + stagingRepository(tempRepoDir.absolutePath) + applyMavenCentralRules = false + sourceJar = false + javadocJar = false + sign = false + checksums = false + verifyPom = false + namespace = "org.junit" + stage = providers.environmentVariable("JRELEASER_MAVENCENTRAL_STAGE") + .map(Stage::of) + .orElse(Stage.UPLOAD) + } + } + } + } +} + +subprojects { + pluginManager.withPlugin("maven-publish") { + configure { + repositories { + maven { + name = "mavenCentralSnapshots" + url = uri("https://central.sonatype.com/repository/maven-snapshots") + credentials { + username = mavenCentralUsername.orNull + password = mavenCentralPassword.orNull + } + } + } + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts b/gradle/plugins/publishing/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts similarity index 75% rename from gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts rename to gradle/plugins/publishing/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts index 3f48f5d656d7..d2c25e9096c8 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts @@ -13,7 +13,10 @@ val clearTempRepoDir by tasks.registering { } } -val verifyArtifactsInStagingRepositoryAreReproducible by tasks.registering(VerifyBinaryArtifactsAreIdentical::class) { +val publishAllSubprojectsToTempRepository by tasks.registering + +tasks.register("verifyArtifactsInStagingRepositoryAreReproducible") { + dependsOn(publishAllSubprojectsToTempRepository) localRepoDir.set(tempRepoDir) } @@ -32,7 +35,7 @@ subprojects { publishingTasks.configureEach { dependsOn(clearTempRepoDir) } - verifyArtifactsInStagingRepositoryAreReproducible { + publishAllSubprojectsToTempRepository { dependsOn(publishingTasks) } } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt similarity index 84% rename from gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt rename to gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt index 9acd67b96c89..293026023a07 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt +++ b/gradle/plugins/publishing/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt @@ -3,8 +3,10 @@ package junitbuild.release import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction @@ -14,8 +16,9 @@ import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse.BodyHandlers +import javax.inject.Inject -abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() { +abstract class VerifyBinaryArtifactsAreIdentical @Inject constructor(providers: ProviderFactory): DefaultTask() { @get:InputDirectory @get:PathSensitive(PathSensitivity.RELATIVE) @@ -24,9 +27,13 @@ abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() { @get:Input abstract val remoteRepoUrl: Property + @get:Internal + abstract val remoteRepoBearerToken: Property + init { // Depends on contents of remote repository outputs.upToDateWhen { false } + remoteRepoBearerToken.convention(providers.environmentVariable("MAVEN_CENTRAL_USER_TOKEN")) } @Suppress("unused") @@ -51,7 +58,10 @@ abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() { val relativeFile = file.relativeTo(localRootDir) val url = URI.create("${baseUrl}/${relativeFile.path}") logger.info("Checking {}...", url) - val request = HttpRequest.newBuilder().GET().uri(url).build() + val request = HttpRequest.newBuilder().GET() + .uri(url) + .header("Authorization", "Bearer ${remoteRepoBearerToken.get()}") + .build() val response = httpClient.send(request, BodyHandlers.ofString()) val remoteSha512 = if (response.statusCode() == 200) response.body() else "status=${response.statusCode()}" if (localSha512 != remoteSha512) { diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 41935db0aa36..bb41fd436361 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,3 +1,11 @@ +pluginManagement { + includeBuild("../base") +} + +plugins { + id("junitbuild.dsl-extensions") apply false +} + dependencyResolutionManagement { versionCatalogs { create("libs") { @@ -11,10 +19,9 @@ dependencyResolutionManagement { rootProject.name = "plugins" -includeBuild("../base") - include("build-parameters") include("common") include("code-generator") +include("publishing") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java index 42835ebd6888..b502f382c321 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestFactory.java @@ -30,7 +30,9 @@ * *

{@code @TestFactory} methods must not be {@code private} or {@code static} * and must return a {@code Stream}, {@code Collection}, {@code Iterable}, - * {@code Iterator}, or array of {@link DynamicNode} instances. Supported + * {@code Iterator}, array of {@link DynamicNode} instances, or any type that + * provides an {@link java.util.Iterator Iterator}-returning {@code iterator()} + * method (such as, for example, a {@code kotlin.sequences.Sequence}). Supported * subclasses of {@code DynamicNode} include {@link DynamicContainer} and * {@link DynamicTest}. Dynamic tests will be executed lazily, * enabling dynamic and even non-deterministic generation of test cases. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java index 43ca0b92555d..1eb301b2d894 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java @@ -10,6 +10,8 @@ package org.junit.jupiter.api.condition; +import static java.util.function.Predicate.isEqual; + import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.function.Function; @@ -42,18 +44,15 @@ protected final IntStream validatedVersions(JRE[] jres, int[] versions) { Preconditions.condition(jres.length > 0 || versions.length > 0, () -> "You must declare at least one JRE or version in @" + this.annotationName); + Preconditions.condition(Arrays.stream(jres).noneMatch(isEqual(JRE.UNDEFINED)), + () -> "JRE.UNDEFINED is not supported in @" + this.annotationName); + Arrays.stream(versions).min().ifPresent(version -> Preconditions.condition(version >= JRE.MINIMUM_VERSION, + () -> String.format("Version [%d] in @%s must be greater than or equal to %d", version, this.annotationName, + JRE.MINIMUM_VERSION))); + return IntStream.concat(// - Arrays.stream(jres).mapToInt(jre -> { - Preconditions.condition(jre != JRE.UNDEFINED, - () -> "JRE.UNDEFINED is not supported in @" + this.annotationName); - return jre.version(); - }), // - Arrays.stream(versions).map(version -> { - Preconditions.condition(version >= JRE.MINIMUM_VERSION, - () -> String.format("Version [%d] in @%s must be greater than or equal to %d", version, - this.annotationName, JRE.MINIMUM_VERSION)); - return version; - })// + Arrays.stream(jres).mapToInt(JRE::version), // + Arrays.stream(versions) // ).distinct(); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java index ea2290e5c8de..7bc7d8a0b117 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java @@ -70,9 +70,6 @@ protected final boolean isCurrentVersionWithinRange(JRE minJre, JRE maxJre, int // Finally, we need to validate the effective minimum and maximum values. Preconditions.condition((min != JRE.MINIMUM_VERSION || max != Integer.MAX_VALUE), () -> "You must declare a non-default value for the minimum or maximum value in @" + this.annotationName); - Preconditions.condition(min >= JRE.MINIMUM_VERSION, - () -> String.format("@%s's minimum value [%d] must greater than or equal to %d", this.annotationName, min, - JRE.MINIMUM_VERSION)); Preconditions.condition(min <= max, () -> String.format("@%s's minimum value [%d] must be less than or equal to its maximum value [%d]", this.annotationName, min, max)); diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java index ae6daf29e2c3..1f1eeda903f4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java @@ -11,6 +11,7 @@ package org.junit.jupiter.api.extension; import static java.util.Collections.unmodifiableList; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; @@ -506,7 +507,7 @@ interface Store { * @deprecated Please extend {@code AutoCloseable} directly. */ @Deprecated - @API(status = STABLE, since = "5.1") + @API(status = DEPRECATED, since = "5.13") interface CloseableResource { /** diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java index aaf90d6db727..144ec6db621c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethod.java @@ -11,14 +11,13 @@ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; -import java.util.Iterator; -import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.jupiter.api.DynamicNode; @@ -40,7 +39,7 @@ public class IsTestFactoryMethod extends IsTestableMethod { private static final String EXPECTED_RETURN_TYPE_MESSAGE = String.format( - "must return a single %1$s or a Stream, Collection, Iterable, Iterator, or array of %1$s", + "must return a single %1$s or a Stream, Collection, Iterable, Iterator, Iterator provider, or array of %1$s", DynamicNode.class.getName()); public IsTestFactoryMethod(DiscoveryIssueReporter issueReporter) { @@ -62,9 +61,7 @@ private static boolean isCompatible(Method method, DiscoveryIssueReporter issueR issueReporter.reportIssue(createTooGenericReturnTypeIssue(method)); return true; } - boolean validContainerType = Stream.class.isAssignableFrom(returnType) // - || Iterable.class.isAssignableFrom(returnType) // - || Iterator.class.isAssignableFrom(returnType); + boolean validContainerType = !returnType.isArray() && isConvertibleToStream(returnType); return validContainerType && isCompatibleContainerType(method, issueReporter); } diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index 80ade03f4b27..7b3d65577455 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.javaModuleName + plugins { id("junitbuild.kotlin-library-conventions") id("junitbuild.shadow-conventions") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java index ee9a2747a6f1..3abbbe760d15 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java @@ -10,6 +10,7 @@ package org.junit.jupiter.params.provider; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.lang.annotation.Annotation; @@ -69,6 +70,7 @@ public Stream provideArguments(ParameterDeclarations parame * instead. */ @Deprecated + @API(status = DEPRECATED, since = "5.13") protected Stream provideArguments(ExtensionContext context, A annotation) { throw new JUnitException(String.format( "AnnotationBasedArgumentsProvider does not override the provideArguments(ParameterDeclarations, ExtensionContext, Annotation) method. " diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java index 4ca4717fd4d7..32666855bb9c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSource.java @@ -44,11 +44,13 @@ * {@link java.util.stream.DoubleStream DoubleStream}, * {@link java.util.stream.LongStream LongStream}, or * {@link java.util.stream.IntStream IntStream}), a {@code Supplier} of an - * {@link java.util.Iterator Iterator}, an array of objects, or an array of - * primitives. Each set of "arguments" within the "stream" can be supplied as an - * instance of {@link Arguments}, an array of objects (for example, {@code Object[]}, - * {@code String[]}, etc.), or a single value if the parameterized - * class or test accepts a single argument. + * {@link java.util.Iterator Iterator}, an array of objects or primitives, or + * any type that provides an {@link java.util.Iterator Iterator}-returning + * {@code iterator()} method (such as, for example, a + * {@code kotlin.sequences.Sequence}). Each set of "arguments" within the + * "stream" can be supplied as an instance of {@link Arguments}, an array of + * objects (for example, {@code Object[]}, {@code String[]}, etc.), or a single + * value if the parameterized class or test accepts a single argument. * *

In contrast to the supported return types for {@link MethodSource @MethodSource} * factory methods, the value of a {@code @FieldSource} field cannot be an instance of diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java index aec745a5188b..d6b4d3599fa3 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSource.java @@ -43,12 +43,13 @@ * {@link java.util.stream.LongStream LongStream}, * {@link java.util.stream.IntStream IntStream}, * {@link java.util.Collection Collection}, - * {@link java.util.Iterator Iterator}, - * {@link Iterable}, an array of objects, or an array of primitives. Each set of - * "arguments" within the "stream" can be supplied as an instance of - * {@link Arguments}, an array of objects (e.g., {@code Object[]}, - * {@code String[]}, etc.), or a single value if the parameterized test - * method accepts a single argument. + * {@link java.util.Iterator Iterator}, an array of objects or primitives, or + * any type that provides an {@link java.util.Iterator Iterator}-returning + * {@code iterator()} method (such as, for example, a + * {@code kotlin.sequences.Sequence}). Each set of "arguments" within the + * "stream" can be supplied as an instance of {@link Arguments}, an array of + * objects (e.g., {@code Object[]}, {@code String[]}, etc.), or a single + * value if the parameterized test method accepts a single argument. * *

If the return type is {@code Stream} or * one of the primitive streams, JUnit will properly close it by calling diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java index 4152d14e8220..ab3d804c5b13 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/SearchOption.java @@ -43,7 +43,8 @@ public enum SearchOption { * @deprecated because it is preferable to inspect the runtime enclosing * types of a class rather than where they are declared. */ - @Deprecated @API(status = DEPRECATED, since = "1.12") + @Deprecated // + @API(status = DEPRECATED, since = "1.12") INCLUDE_ENCLOSING_CLASSES } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java index e12d0421f286..c50a4b178518 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CollectionUtils.java @@ -16,8 +16,10 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.StreamSupport.stream; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; import java.lang.reflect.Array; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -36,6 +38,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; /** * Collection of utilities for working with {@link Collection Collections}. @@ -161,7 +164,8 @@ public static boolean isConvertibleToStream(Class type) { || Iterable.class.isAssignableFrom(type)// || Iterator.class.isAssignableFrom(type)// || Object[].class.isAssignableFrom(type)// - || (type.isArray() && type.getComponentType().isPrimitive())); + || (type.isArray() && type.getComponentType().isPrimitive())// + || findIteratorMethod(type).isPresent()); } /** @@ -177,6 +181,9 @@ public static boolean isConvertibleToStream(Class type) { *

  • {@link Iterator}
  • *
  • {@link Object} array
  • *
  • primitive array
  • + *
  • any type that provides an + * {@link java.util.Iterator Iterator}-returning {@code iterator()} method + * (such as, for example, a {@code kotlin.sequences.Sequence})
  • * * * @param object the object to convert into a stream; never {@code null} @@ -223,8 +230,21 @@ public static Stream toStream(Object object) { if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) { return IntStream.range(0, Array.getLength(object)).mapToObj(i -> Array.get(object, i)); } - throw new PreconditionViolationException( - "Cannot convert instance of " + object.getClass().getName() + " into a Stream: " + object); + return tryConvertToStreamByReflection(object); + } + + private static Stream tryConvertToStreamByReflection(Object object) { + return findIteratorMethod(object.getClass()) // + .map(method -> (Iterator) invokeMethod(method, object)) // + .map(iterator -> spliteratorUnknownSize(iterator, ORDERED)) // + .map(spliterator -> stream(spliterator, false)) // + .orElseThrow(() -> new PreconditionViolationException(String.format( + "Cannot convert instance of %s into a Stream: %s", object.getClass().getName(), object))); + } + + private static Optional findIteratorMethod(Class type) { + return ReflectionSupport.findMethod(type, "iterator") // + .filter(method -> method.getReturnType() == Iterator.class); } /** diff --git a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts index 99ed213d9837..503f1a4a744c 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -1,3 +1,4 @@ +import junitbuild.extensions.dependencyProject import junitbuild.java.WriteArtifactsFile plugins { diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index cd256959b629..7a032e5ae482 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -1,3 +1,4 @@ +import junitbuild.extensions.javaModuleName import junitbuild.java.UpdateJarAction plugins { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java index b356e376b54c..a18a71b45c74 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherConstants.java @@ -225,9 +225,10 @@ public class LauncherConstants { * *

    If an engine reports an issue with a severity equal to or higher than * the configured critical severity, its tests will not be executed. - * Instead, the engine will be reported as failed during execution with a + * Depending on {@link #DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME}, a * {@link org.junit.platform.launcher.core.DiscoveryIssueException} listing - * all critical issues. + * all critical issues will be thrown during discovery or be reported as + * engine-level failure during execution. * *

    Supported Values

    * @@ -244,6 +245,27 @@ public class LauncherConstants { @API(status = EXPERIMENTAL, since = "1.13") public static final String CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME = "junit.platform.discovery.issue.severity.critical"; + /** + * Property name used to configure the phase that critical discovery issues + * should cause a failure + * + *

    Supported Values

    + * + *

    Supported values are "discovery" or "execution". + * + *

    If not specified, the {@code Launcher} will report discovery issues + * during the discovery phase if + * {@link Launcher#discover(LauncherDiscoveryRequest)} is called, and during + * the execution phase if + * {@link Launcher#execute(LauncherDiscoveryRequest, TestExecutionListener...)} + * is called. + * + * @since 1.13 + * @see #CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.13") + public static final String DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME = "junit.platform.discovery.issue.failure.phase"; + private LauncherConstants() { /* no-op */ } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java index 2a9fa3bf1ed4..c4691503ba1f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncher.java @@ -12,8 +12,8 @@ import static java.util.Collections.unmodifiableCollection; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; +import static org.junit.platform.launcher.core.LauncherPhase.DISCOVERY; +import static org.junit.platform.launcher.core.LauncherPhase.EXECUTION; import java.util.Collection; @@ -100,8 +100,7 @@ public void execute(TestPlan testPlan, TestExecutionListener... listeners) { execute((InternalTestPlan) testPlan, listeners); } - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, - EngineDiscoveryOrchestrator.Phase phase) { + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, LauncherPhase phase) { return discoveryOrchestrator.discover(discoveryRequest, phase); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java index 00bcdae9ada8..5c989b32d79a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryOrchestrator.java @@ -13,12 +13,12 @@ import static java.util.stream.Collectors.joining; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.Filter.composeFilters; +import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -28,6 +28,7 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.UnrecoverableExceptions; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.Filter; import org.junit.platform.engine.FilterResult; import org.junit.platform.engine.TestDescriptor; @@ -68,15 +69,19 @@ public EngineDiscoveryOrchestrator(Iterable testEngines, } /** - * Discovers tests for the supplied request in the supplied phase using the - * configured test engines. + * Discovers tests for the supplied request using the configured test + * engines. * *

    Applies {@linkplain org.junit.platform.launcher.EngineFilter engine * filters} and {@linkplain PostDiscoveryFilter post-discovery filters} and * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. */ - public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase) { - return discover(request, phase, UniqueId::forEngine); + public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request) { + return discover(request, Optional.empty(), UniqueId::forEngine); + } + + LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, LauncherPhase phase) { + return discover(request, Optional.of(phase), UniqueId::forEngine); } /** @@ -93,12 +98,12 @@ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase * {@link EngineExecutionOrchestrator} will not emit start or emit events * for engines without tests. */ - public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) { - LauncherDiscoveryResult result = discover(request, phase, parentId::appendEngine); + public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, UniqueId parentId) { + LauncherDiscoveryResult result = discover(request, Optional.empty(), parentId::appendEngine); return result.withRetainedEngines(TestDescriptor::containsTests); } - private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Optional phase, Function uniqueIdCreator) { DiscoveryIssueCollector issueCollector = new DiscoveryIssueCollector(request.getConfigurationParameters()); LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request, issueCollector); @@ -109,19 +114,48 @@ public LauncherDiscoveryListener getDiscoveryListener() { } }; listener.launcherDiscoveryStarted(request); + LauncherDiscoveryResult discoveryResult; try { Map testEngineResults = discoverSafely(delegatingRequest, phase, issueCollector, uniqueIdCreator); - return new LauncherDiscoveryResult(testEngineResults, request.getConfigurationParameters(), + discoveryResult = new LauncherDiscoveryResult(testEngineResults, request.getConfigurationParameters(), request.getOutputDirectoryProvider()); } finally { listener.launcherDiscoveryFinished(request); } + if (shouldReportDiscoveryIssues(request, phase)) { + reportDiscoveryIssues(discoveryResult); + } + return discoveryResult; } - private Map discoverSafely(LauncherDiscoveryRequest request, Phase phase, - DiscoveryIssueCollector issueCollector, Function uniqueIdCreator) { + private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryRequest request, + Optional phase) { + ConfigurationParameters configurationParameters = request.getConfigurationParameters(); + return getDiscoveryIssueFailurePhase(configurationParameters).orElse( + phase.orElse(null)) == LauncherPhase.DISCOVERY; + } + + private static void reportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { + DiscoveryIssueException exception = null; + for (TestEngine testEngine : discoveryResult.getTestEngines()) { + EngineResultInfo engineResult = discoveryResult.getEngineResult(testEngine); + DiscoveryIssueNotifier discoveryIssueNotifier = engineResult.getDiscoveryIssueNotifier(); + discoveryIssueNotifier.logCriticalIssues(testEngine); + discoveryIssueNotifier.logNonCriticalIssues(testEngine); + if (exception == null) { + exception = discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine); + } + } + if (exception != null) { + throw exception; + } + } + + private Map discoverSafely(LauncherDiscoveryRequest request, + Optional phase, DiscoveryIssueCollector issueCollector, + Function uniqueIdCreator) { Map testEngineDescriptors = new LinkedHashMap<>(); EngineFilterer engineFilterer = new EngineFilterer(request.getEngineFilters()); @@ -129,14 +163,13 @@ private Map discoverSafely(LauncherDiscoveryReques boolean engineIsExcluded = engineFilterer.isExcluded(testEngine); if (engineIsExcluded) { - logger.debug(() -> String.format( - "Test discovery for engine '%s' was skipped due to an EngineFilter in %s phase.", - testEngine.getId(), phase)); + logger.debug(() -> String.format("Test discovery for engine '%s' was skipped due to an EngineFilter%s.", + testEngine.getId(), phase.map(it -> String.format(" in %s phase", it)).orElse(""))); continue; } - logger.debug(() -> String.format("Discovering tests during Launcher %s phase in engine '%s'.", phase, - testEngine.getId())); + logger.debug(() -> String.format("Discovering tests%s in engine '%s'.", + phase.map(it -> String.format(" during Launcher %s phase", it)).orElse(""), testEngine.getId())); EngineResultInfo engineResult = discoverEngineRoot(testEngine, request, issueCollector, uniqueIdCreator); testEngineDescriptors.put(testEngine, engineResult); @@ -241,13 +274,4 @@ private void acceptInAllTestEngines(Map testEngine testEngineResults.values().forEach(result -> result.getRootDescriptor().accept(visitor)); } - public enum Phase { - DISCOVERY, EXECUTION; - - @Override - public String toString() { - return name().toLowerCase(Locale.ENGLISH); - } - } - } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java index eccb29fd82df..1cf63758f3aa 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineExecutionOrchestrator.java @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME; +import static org.junit.platform.launcher.core.LauncherPhase.getDiscoveryIssueFailurePhase; import static org.junit.platform.launcher.core.ListenerRegistry.forEngineExecutionListeners; import java.util.Optional; @@ -185,7 +186,9 @@ private static EngineExecutionListener selectExecutionListener(EngineExecutionLi private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, EngineExecutionListener listener, TestEngine testEngine, NamespacedHierarchicalStore requestLevelStore) { EngineResultInfo engineDiscoveryResult = discoveryResult.getEngineResult(testEngine); - DiscoveryIssueNotifier discoveryIssueNotifier = engineDiscoveryResult.getDiscoveryIssueNotifier(); + DiscoveryIssueNotifier discoveryIssueNotifier = shouldReportDiscoveryIssues(discoveryResult) // + ? engineDiscoveryResult.getDiscoveryIssueNotifier() // + : DiscoveryIssueNotifier.NO_ISSUES; TestDescriptor engineDescriptor = engineDiscoveryResult.getRootDescriptor(); Throwable failure = engineDiscoveryResult.getCause() // .orElseGet(() -> discoveryIssueNotifier.createExceptionForCriticalIssues(testEngine)); @@ -203,6 +206,12 @@ private void failOrExecuteEngine(LauncherDiscoveryResult discoveryResult, Engine } } + private static boolean shouldReportDiscoveryIssues(LauncherDiscoveryResult discoveryResult) { + ConfigurationParameters configurationParameters = discoveryResult.getConfigurationParameters(); + return getDiscoveryIssueFailurePhase(configurationParameters).orElse( + LauncherPhase.EXECUTION) == LauncherPhase.EXECUTION; + } + private ListenerRegistry buildListenerRegistryForExecution( TestExecutionListener... listeners) { if (listeners.length == 0) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java new file mode 100644 index 000000000000..107bcf971c94 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherPhase.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; + +import java.util.Locale; +import java.util.Optional; + +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.engine.ConfigurationParameters; + +/** + * The phase the {@link org.junit.platform.launcher.Launcher} is in. + * + * @since 1.13 + */ +enum LauncherPhase { + + DISCOVERY, EXECUTION; + + private static final Logger logger = LoggerFactory.getLogger(LauncherPhase.class); + + static Optional getDiscoveryIssueFailurePhase(ConfigurationParameters configurationParameters) { + return configurationParameters.get(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, value -> { + try { + return LauncherPhase.valueOf(value.toUpperCase(Locale.ROOT)); + } + catch (Exception e) { + logger.warn( + () -> String.format("Ignoring invalid LauncherPhase '%s' set via the '%s' configuration parameter.", + value, DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME)); + return null; + } + }); + } + + @Override + public String toString() { + return name().toLowerCase(Locale.ENGLISH); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java index 757d17f6f400..d6923652c749 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LauncherDiscoveryListeners.java @@ -21,9 +21,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.engine.SelectorResolutionResult.Status; import org.junit.platform.engine.TestEngine; -import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.launcher.LauncherDiscoveryListener; /** @@ -46,13 +44,6 @@ private LauncherDiscoveryListeners() { * *

      *
    • - * a {@linkplain Status#FAILED failed} resolution result. - *
    • - *
    • - * an {@linkplain Status#FAILED unresolved} resolution result for a - * {@link UniqueIdSelector} that starts with the engine's unique ID. - *
    • - *
    • * any recoverable {@link Throwable} thrown by * {@link TestEngine#discover}. *
    • diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts index f1dede61555d..5f11aab2adf9 100644 --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -1,3 +1,5 @@ +import junitbuild.extensions.javaModuleName + plugins { id("junitbuild.java-library-conventions") id("junitbuild.shadow-conventions") diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java index 79e8ef209728..44985338fd1b 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java +++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilder.java @@ -11,6 +11,7 @@ package org.junit.platform.suite.commons; import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.DEPRECATED; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; @@ -286,6 +287,7 @@ public SuiteLauncherDiscoveryRequestBuilder listener(LauncherDiscoveryListener l * {@link #applySelectorsAndFiltersFromSuite} */ @Deprecated + @API(status = DEPRECATED, since = "1.11") public SuiteLauncherDiscoveryRequestBuilder suite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); applyConfigurationParametersFromSuite(suiteClass); diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java index 6e3bf311ccfb..640d90f984e1 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteLauncher.java @@ -23,7 +23,6 @@ import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; -import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; import org.junit.platform.launcher.core.LauncherDiscoveryResult; import org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry; @@ -56,7 +55,7 @@ private boolean hasTestEngineOtherThanSuiteEngine(Set testEngines) { } LauncherDiscoveryResult discover(LauncherDiscoveryRequest discoveryRequest, UniqueId parentId) { - return discoveryOrchestrator.discover(discoveryRequest, Phase.DISCOVERY, parentId); + return discoveryOrchestrator.discover(discoveryRequest, parentId); } TestExecutionSummary execute(LauncherDiscoveryResult discoveryResult, diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java index 9f7f3e18501a..37398b4efd3e 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineTestKit.java @@ -17,8 +17,6 @@ import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CloseAction.closeAutoCloseables; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.DISCOVERY; -import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; import java.nio.file.Path; import java.util.List; @@ -190,7 +188,7 @@ public static EngineDiscoveryResults discover(String engineId, LauncherDiscovery public static EngineDiscoveryResults discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest) { Preconditions.notNull(testEngine, "TestEngine must not be null"); Preconditions.notNull(discoveryRequest, "EngineDiscoveryRequest must not be null"); - LauncherDiscoveryResult discoveryResult = discover(testEngine, discoveryRequest, DISCOVERY); + LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); List discoveryIssues = discoveryResult.getDiscoveryIssues(testEngine); return new EngineDiscoveryResults(engineDescriptor, discoveryIssues); @@ -337,7 +335,7 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques private static void executeUsingLauncherOrchestration(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, EngineExecutionListener listener) { - LauncherDiscoveryResult discoveryResult = discover(testEngine, discoveryRequest, EXECUTION); + LauncherDiscoveryResult discoveryResult = discoverUsingOrchestrator(testEngine, discoveryRequest); TestDescriptor engineTestDescriptor = discoveryResult.getEngineTestDescriptor(testEngine); Preconditions.notNull(engineTestDescriptor, "TestEngine did not yield a TestDescriptor"); withRequestLevelStore(store -> new EngineExecutionOrchestrator().execute(discoveryResult, listener, store)); @@ -354,10 +352,10 @@ private static NamespacedHierarchicalStore newStore(NamespacedHierarc return new NamespacedHierarchicalStore<>(parentStore, closeAutoCloseables()); } - private static LauncherDiscoveryResult discover(TestEngine testEngine, LauncherDiscoveryRequest discoveryRequest, - EngineDiscoveryOrchestrator.Phase phase) { + private static LauncherDiscoveryResult discoverUsingOrchestrator(TestEngine testEngine, + LauncherDiscoveryRequest discoveryRequest) { return new EngineDiscoveryOrchestrator(singleton(testEngine), emptySet()) // - .discover(discoveryRequest, phase); + .discover(discoveryRequest); } @SuppressWarnings("unchecked") diff --git a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte index c84b159c4078..509b5d47fa48 100644 --- a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte +++ b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte @@ -50,7 +50,7 @@ class DisabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") - @DisabledOnJre(versions = 7) + @DisabledOnJre(value = JAVA_17, versions = { 21, 7 }) void version7() { } diff --git a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte index c3b71df58ab4..651345ea02b3 100644 --- a/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte +++ b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte @@ -49,7 +49,7 @@ class EnabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") - @EnabledOnJre(versions = 7) + @EnabledOnJre(value = JAVA_17, versions = { 21, 7 }) void version7() { } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java index df84f44540a2..ec30168e7605 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java @@ -752,6 +752,7 @@ void chars() { // ------------------------------------------------------------------------- + @SuppressWarnings("overrides") private static class EqualsThrowsException { @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java index 450653ecd327..842e7f463a8f 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java @@ -757,6 +757,7 @@ void chars() { // ------------------------------------------------------------------------- + @SuppressWarnings("overrides") private static class EqualsThrowsExceptionClass { @Override diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 35a2e8941a73..0dcd074d74f8 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -82,7 +82,7 @@ protected EngineDiscoveryResults discoverTests(LauncherDiscoveryRequest request) return EngineTestKit.discover(this.engine, request); } - private static LauncherDiscoveryRequestBuilder defaultRequest() { + protected static LauncherDiscoveryRequestBuilder defaultRequest() { return request() // .outputDirectoryProvider(dummyOutputDirectoryProvider()) // .configurationParameter(STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME, String.valueOf(false)) // diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java index 8090690f32f6..e121fbd683aa 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.util.ArrayList; @@ -129,6 +130,7 @@ private void performAssertions(Class testClass, Map configPar request() .selectors(selectClass(testClass)) .configurationParameters(configParams) + .configurationParameter(DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "execution") .build() ); // @formatter:on diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index 697efda838a4..f607f28e15b9 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -819,7 +819,7 @@ private List uniqueIds() { } private LauncherDiscoveryRequestBuilder request() { - return LauncherDiscoveryRequestBuilder.request() // + return defaultRequest() // .configurationParameter(DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME, "logging") // .listeners(discoveryListener); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java index ab2d904f3253..26db9306abb9 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -25,7 +25,6 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectNestedMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -62,21 +61,21 @@ class DiscoveryTests extends AbstractJupiterTestEngineTests { @Test void discoverTestClass() { - LauncherDiscoveryRequest request = request().selectors(selectClass(LocalTestCase.class)).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(LocalTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(7, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void doNotDiscoverAbstractTestClass() { - LauncherDiscoveryRequest request = request().selectors(selectClass(AbstractTestCase.class)).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors(selectClass(AbstractTestCase.class)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(0, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMethodByUniqueId() { - LauncherDiscoveryRequest request = request().selectors( + LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test1()"))).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); @@ -84,7 +83,7 @@ void discoverMethodByUniqueId() { @Test void discoverMethodByUniqueIdForOverloadedMethod() { - LauncherDiscoveryRequest request = request().selectors( + LauncherDiscoveryRequest request = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test4()"))).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); @@ -92,8 +91,9 @@ void discoverMethodByUniqueIdForOverloadedMethod() { @Test void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { - LauncherDiscoveryRequest request = request().selectors(selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod( - LocalTestCase.class, "test4(" + TestInfo.class.getName() + ")"))).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors( + selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, + "test4(" + TestInfo.class.getName() + ")"))).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @@ -102,14 +102,15 @@ void discoverMethodByUniqueIdForOverloadedMethodVariantThatAcceptsArguments() { void discoverMethodByMethodReference() throws NoSuchMethodException { Method testMethod = LocalTestCase.class.getDeclaredMethod("test3"); - LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, testMethod)).build(); + LauncherDiscoveryRequest request = defaultRequest().selectors( + selectMethod(LocalTestCase.class, testMethod)).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); assertEquals(2, engineDescriptor.getDescendants().size(), "# resolved test descriptors"); } @Test void discoverMultipleMethodsOfSameClass() { - LauncherDiscoveryRequest request = request().selectors(selectMethod(LocalTestCase.class, "test1"), + LauncherDiscoveryRequest request = defaultRequest().selectors(selectMethod(LocalTestCase.class, "test1"), selectMethod(LocalTestCase.class, "test2")).build(); TestDescriptor engineDescriptor = discoverTests(request).getEngineDescriptor(); @@ -121,7 +122,7 @@ void discoverMultipleMethodsOfSameClass() { @Test void discoverCompositeSpec() { - LauncherDiscoveryRequest spec = request().selectors( + LauncherDiscoveryRequest spec = defaultRequest().selectors( selectUniqueId(JupiterUniqueIdBuilder.uniqueIdForMethod(LocalTestCase.class, "test2()")), selectClass(LocalTestCase.class)).build(); @@ -131,7 +132,7 @@ void discoverCompositeSpec() { @Test void discoverTestTemplateMethodByUniqueId() { - LauncherDiscoveryRequest spec = request().selectors( + LauncherDiscoveryRequest spec = defaultRequest().selectors( selectUniqueId(uniqueIdForTestTemplateMethod(TestTemplateClass.class, "testTemplate()"))).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); @@ -140,7 +141,7 @@ void discoverTestTemplateMethodByUniqueId() { @Test void discoverTestTemplateMethodByMethodSelector() { - LauncherDiscoveryRequest spec = request().selectors( + LauncherDiscoveryRequest spec = defaultRequest().selectors( selectMethod(TestTemplateClass.class, "testTemplate")).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); @@ -153,7 +154,7 @@ void discoverDeeplyNestedTestMethodByNestedMethodSelector() throws Exception { List.of(TestCaseWithExtendedNested.class, TestCaseWithExtendedNested.ConcreteInner1.class), AbstractSuperClass.NestedInAbstractClass.class, AbstractSuperClass.NestedInAbstractClass.class.getDeclaredMethod("test")); - LauncherDiscoveryRequest spec = request().selectors(selector).build(); + LauncherDiscoveryRequest spec = defaultRequest().selectors(selector).build(); TestDescriptor engineDescriptor = discoverTests(spec).getEngineDescriptor(); @@ -198,12 +199,12 @@ void reportsWarningForTestClassWithInvalidTestMethod(LauncherDiscoveryRequest re static List> requestsForTestClassWithInvalidTestMethod() { return List.of( // named("directly selected", - request().selectors(selectClass(InvalidTestCases.InvalidTestMethodTestCase.class)).build()), // - named("indirectly selected", request() // + defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestMethodTestCase.class)).build()), // + named("indirectly selected", defaultRequest() // .selectors(selectPackage(InvalidTestCases.InvalidTestMethodTestCase.class.getPackageName())) // .filters(includeClassNamePatterns( Pattern.quote(InvalidTestCases.InvalidTestMethodTestCase.class.getName()))).build()), // - named("subclasses", request() // + named("subclasses", defaultRequest() // .selectors(selectClass(InvalidTestCases.InvalidTestMethodSubclass1TestCase.class), selectClass(InvalidTestCases.InvalidTestMethodSubclass2TestCase.class)) // .build()) // @@ -229,14 +230,14 @@ void reportsWarningForInvalidStandaloneTestClass(LauncherDiscoveryRequest reques static List requestsForTestClassWithInvalidStandaloneTestClass() { return List.of( // argumentSet("directly selected", - request().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.class)).build(), + defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.class)).build(), InvalidTestCases.InvalidTestClassTestCase.class), // - argumentSet("indirectly selected", request() // + argumentSet("indirectly selected", defaultRequest() // .selectors(selectPackage(InvalidTestCases.InvalidTestClassTestCase.class.getPackageName())) // .filters(includeClassNamePatterns( Pattern.quote(InvalidTestCases.InvalidTestClassTestCase.class.getName()))).build(), // InvalidTestCases.InvalidTestClassTestCase.class), // - argumentSet("subclass", request() // + argumentSet("subclass", defaultRequest() // .selectors(selectClass(InvalidTestCases.InvalidTestClassSubclassTestCase.class)) // .build(), // InvalidTestCases.InvalidTestClassSubclassTestCase.class) // @@ -262,8 +263,8 @@ void reportsWarningForInvalidNestedTestClass(LauncherDiscoveryRequest request) { static List> requestsForTestClassWithInvalidNestedTestClass() { return List.of( // named("directly selected", - request().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.Inner.class)).build()), // - named("subclass", request() // + defaultRequest().selectors(selectClass(InvalidTestCases.InvalidTestClassTestCase.Inner.class)).build()), // + named("subclass", defaultRequest() // .selectors(selectNestedClass(List.of(InvalidTestCases.InvalidTestClassSubclassTestCase.class), InvalidTestCases.InvalidTestClassTestCase.Inner.class)) // .build()) // diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java index f7b7534e38be..22c295d74864 100644 --- a/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java @@ -66,7 +66,8 @@ void invalidFactoryMethods(String methodName) { var issue = getOnlyElement(discoveryIssues); assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.WARNING); assertThat(issue.message()).isEqualTo( - "@TestFactory method '%s' must return a single org.junit.jupiter.api.DynamicNode or a Stream, Collection, Iterable, Iterator, or array of org.junit.jupiter.api.DynamicNode. " + "@TestFactory method '%s' must return a single org.junit.jupiter.api.DynamicNode or a " + + "Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode. " + "It will not be executed.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); @@ -83,7 +84,8 @@ void suspiciousFactoryMethods(String methodName) { assertThat(issue.severity()).isEqualTo(DiscoveryIssue.Severity.INFO); assertThat(issue.message()).isEqualTo( "The declared return type of @TestFactory method '%s' does not support static validation. " - + "It must return a single org.junit.jupiter.api.DynamicNode or a Stream, Collection, Iterable, Iterator, or array of org.junit.jupiter.api.DynamicNode.", + + "It must return a single org.junit.jupiter.api.DynamicNode or a " + + "Stream, Collection, Iterable, Iterator, Iterator provider, or array of org.junit.jupiter.api.DynamicNode.", method.toGenericString()); assertThat(issue.source()).contains(MethodSource.from(method)); } diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt new file mode 100644 index 000000000000..ba0dec561903 --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinDynamicTests.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DynamicTest.dynamicTest +import java.math.BigDecimal +import java.math.BigDecimal.ONE +import java.math.MathContext +import java.math.BigInteger as BigInt +import java.math.RoundingMode as Rounding + +/** + * Unit tests for JUnit Jupiter [TestFactory] use in kotlin classes. + * + * @since 5.12 + */ +class KotlinDynamicTests { + @Nested + inner class SequenceReturningTestFactoryTests { + @TestFactory + fun `Dynamic tests returned as Kotlin sequence`() = + generateSequence(0) { it + 2 } + .map { dynamicTest("$it should be even") { assertEquals(0, it % 2) } } + .take(10) + + @TestFactory + fun `Consecutive fibonacci nr ratios, should converge to golden ratio as n increases`(): Sequence { + val scale = 5 + val goldenRatio = + (ONE + 5.toBigDecimal().sqrt(MathContext(scale + 10, Rounding.HALF_UP))) + .divide(2.toBigDecimal(), scale, Rounding.HALF_UP) + + fun shouldApproximateGoldenRatio( + cur: BigDecimal, + next: BigDecimal + ) = next.divide(cur, scale, Rounding.HALF_UP).let { + dynamicTest("$cur / $next = $it should approximate the golden ratio in $scale decimals") { + assertEquals(goldenRatio, it) + } + } + return generateSequence(BigInt.ONE to BigInt.ONE) { (cur, next) -> next to cur + next } + .map { (cur) -> cur.toBigDecimal() } + .zipWithNext(::shouldApproximateGoldenRatio) + .drop(14) + .take(10) + } + } +} diff --git a/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt new file mode 100644 index 000000000000..800407845e11 --- /dev/null +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestKotlinSequenceIntegrationTests.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +package org.junit.jupiter.params + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.FieldSource +import org.junit.jupiter.params.provider.MethodSource +import java.time.Month + +/** + * Tests for Kotlin compatibility of ParameterizedTest + */ +object ParameterizedTestKotlinSequenceIntegrationTests { + @ParameterizedTest + @MethodSource("dataProvidedByKotlinSequenceMethod") + fun `a method source can be supplied by a Sequence-returning method`( + value: Int, + month: Month + ) { + assertEquals(value, month.value) + } + + @JvmStatic + private fun dataProvidedByKotlinSequenceMethod() = dataProvidedByKotlinSequenceField + + @JvmStatic + val dataProvidedByKotlinSequenceField = + sequenceOf( + arguments(1, Month.JANUARY), + arguments(3, Month.MARCH), + arguments(8, Month.AUGUST), + arguments(5, Month.MAY), + arguments(12, Month.DECEMBER) + ) + + @ParameterizedTest + @FieldSource("dataProvidedByKotlinSequenceField") + fun `a field source can be supplied by a Sequence-typed field`( + value: Int, + month: Month + ) { + assertEquals(value, month.value) + } +} diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 3e63fe5fe14f..38e1b9ed1e4e 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -1,5 +1,5 @@ - import junitbuild.extensions.capitalized +import junitbuild.extensions.dependencyProject import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.internal.os.OperatingSystem diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java index e679f3d4d531..cc2e8b38469d 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverterTests.java @@ -167,6 +167,11 @@ public boolean equals(Object obj) { return Objects.equals(this.title, that.title); } + @Override + public int hashCode() { + return Objects.hash(title); + } + } static class Journal { @@ -188,6 +193,11 @@ public boolean equals(Object obj) { return Objects.equals(this.title, that.title); } + @Override + public int hashCode() { + return Objects.hash(title); + } + } static class Newspaper { @@ -217,6 +227,11 @@ public boolean equals(Object obj) { return Objects.equals(this.title, that.title); } + @Override + public int hashCode() { + return Objects.hash(title); + } + } static class Magazine { diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java index 9e6f01daccbb..749f62161073 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/CollectionUtilsTests.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.Spliterator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.DoubleStream; import java.util.stream.IntStream; @@ -139,6 +140,7 @@ class StreamConversion { Collection.class, // Iterable.class, // Iterator.class, // + IteratorProvider.class, // Object[].class, // String[].class, // int[].class, // @@ -160,10 +162,11 @@ static Stream objectsConvertibleToStreams() { Stream.of("cat", "dog"), // DoubleStream.of(42.3), // IntStream.of(99), // - LongStream.of(100000000), // + LongStream.of(100_000_000), // Set.of(1, 2, 3), // Arguments.of((Object) new Object[] { 9, 8, 7 }), // - new int[] { 5, 10, 15 }// + new int[] { 5, 10, 15 }, // + new IteratorProvider(1, 2, 3, 4, 5)// ); } @@ -174,6 +177,8 @@ static Stream objectsConvertibleToStreams() { Object.class, // Integer.class, // String.class, // + UnusableIteratorProvider.class, // + Spliterator.class, // int.class, // boolean.class // }) @@ -242,16 +247,10 @@ void toStreamWithLongStream() { } @Test - @SuppressWarnings({ "unchecked", "serial" }) + @SuppressWarnings({ "unchecked" }) void toStreamWithCollection() { var collectionStreamClosed = new AtomicBoolean(false); - Collection input = new ArrayList<>() { - - { - add("foo"); - add("bar"); - } - + var input = new ArrayList<>(List.of("foo", "bar")) { @Override public Stream stream() { return super.stream().onClose(() -> collectionStreamClosed.set(true)); @@ -287,6 +286,25 @@ void toStreamWithIterator() { assertThat(result).containsExactly("foo", "bar"); } + @Test + @SuppressWarnings("unchecked") + void toStreamWithIteratorProvider() { + var input = new IteratorProvider("foo", "bar"); + + var result = (Stream) CollectionUtils.toStream(input); + + assertThat(result).containsExactly("foo", "bar"); + } + + @Test + void throwWhenIteratorNamedMethodDoesNotReturnAnIterator() { + var o = new UnusableIteratorProvider("Test"); + var e = assertThrows(PreconditionViolationException.class, () -> CollectionUtils.toStream(o)); + + assertEquals("Cannot convert instance of %s into a Stream: %s".formatted( + UnusableIteratorProvider.class.getName(), o), e.getMessage()); + } + @Test @SuppressWarnings("unchecked") void toStreamWithArray() { @@ -355,4 +373,26 @@ public Object convert(Object source, ParameterContext context) throws ArgumentCo } } } + + /** + * An interface that has a method with name 'iterator', returning a java.util/Iterator as a return type + */ + private record IteratorProvider(Object... elements) { + + @SuppressWarnings("unused") + Iterator iterator() { + return Arrays.stream(elements).iterator(); + } + } + + /** + * An interface that has a method with name 'iterator', but does not return java.util/Iterator as a return type + */ + private record UnusableIteratorProvider(Object... elements) { + + @SuppressWarnings("unused") + Object iterator() { + return Arrays.stream(elements).iterator(); + } + } } diff --git a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java index e893428d0246..c734d70e06f4 100644 --- a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingDiscoveryListenerIntegrationTests.java @@ -57,6 +57,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId EngineTestKit.discover(testEngine, request() // .selectors(selectClass(FlightRecordingDiscoveryListenerIntegrationTests.class)) // .listeners(new FlightRecordingDiscoveryListener()) // + .enableImplicitConfigurationParameters(false) // .build()); jfrEvents.awaitEvents(); 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 1722bd9bfc42..d03bc43c3a0d 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 @@ -25,6 +25,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatCannotResolveAnything; import static org.junit.platform.fakes.FaultyTestEngines.createEngineThatFailsToResolveAnything; +import static org.junit.platform.launcher.LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.DRY_RUN_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.DEFAULT_DISCOVERY_LISTENER_CONFIGURATION_PROPERTY_NAME; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -948,6 +949,55 @@ public void execute(ExecutionRequest request) { LauncherConstants.CRITICAL_DISCOVERY_ISSUE_SEVERITY_PROPERTY_NAME); } + @Test + void failsDuringDiscoveryIfConfigurationParameterIsSetAccordingly() { + + var engine = new TestEngineStub("engine-id") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var listener = discoveryRequest.getDiscoveryListener(); + listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.ERROR, "error")); + return new EngineDescriptor(uniqueId, "Engine"); + } + }; + + var exception = assertThrows(DiscoveryIssueException.class, () -> execute(engine, request -> request // + .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, "discovery"))); + + assertThat(exception) // + .isInstanceOf(DiscoveryIssueException.class) // + .hasMessageStartingWith( + "TestEngine with ID 'engine-id' encountered a critical issue during test discovery") // + .hasMessageContaining("(1) [ERROR] error"); + } + + @ParameterizedTest + @ValueSource(strings = { "discovery", "execution" }) + void logsNonCriticalIssuesOnlyOnce(String phase, @TrackLogRecords LogRecordListener listener) { + + var engine = new TestEngineStub("engine-id") { + @Override + public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { + var listener = discoveryRequest.getDiscoveryListener(); + listener.issueEncountered(uniqueId, DiscoveryIssue.create(Severity.WARNING, "warning")); + return new EngineDescriptor(uniqueId, "Engine"); + } + + @Override + public void execute(ExecutionRequest request) { + var executionListener = request.getEngineExecutionListener(); + var engineDescriptor = request.getRootTestDescriptor(); + executionListener.executionStarted(engineDescriptor); + executionListener.executionFinished(engineDescriptor, successful()); + } + }; + + execute(engine, request -> request // + .configurationParameter(LauncherConstants.DISCOVERY_ISSUE_FAILURE_PHASE_PROPERTY_NAME, phase)); + + assertThat(listener.stream(DiscoveryIssueNotifier.class, Level.WARNING)).hasSize(1); + } + private static ReportedData execute(TestEngine engine) { return execute(engine, identity()); } @@ -969,6 +1019,7 @@ private static ReportedData execute(TestEngine engine, UnaryOperator mockedConstruction = mockConstruction( diff --git a/platform-tests/src/test/resources/log4j2-test.xml b/platform-tests/src/test/resources/log4j2-test.xml index 544a157b1d8f..636f0c345058 100644 --- a/platform-tests/src/test/resources/log4j2-test.xml +++ b/platform-tests/src/test/resources/log4j2-test.xml @@ -11,6 +11,7 @@ + diff --git a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts index 94bc99c60916..2f2d9cd00a68 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -1,6 +1,6 @@ - import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigurationInternal import junitbuild.extensions.capitalized +import junitbuild.extensions.dependencyProject import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.kotlin.dsl.support.listFilesOrdered import java.time.Duration diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts index 0fef24865e1f..b8a6f83ed106 100644 --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "2.1.20" + kotlin("jvm") version "2.1.21" } repositories { diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java index 96a657d750c8..2525a5c8c588 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java @@ -32,7 +32,7 @@ public MavenRepoProxy() throws IOException { @SuppressWarnings("unused") public MavenRepoProxy(int port) throws IOException { - this("https://oss.sonatype.org/content/repositories/snapshots", port); + this("https://central.sonatype.com/repository/maven-snapshots", port); } private MavenRepoProxy(String proxiedUrl, int port) throws IOException {