diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0786ce3ef470..3982c296a43f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,8 +1 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel custom: https://junit.org/sponsoring diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index f1ce8b0c8472..000000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,23 +0,0 @@ -## Overview - -_Replace the following bullet points with your issue description, -after answering yourself: **"What kind of issue is this?"**_ - -- ( ) **Question.** This issue tracker is not the place for questions. -If you want to ask how to do something, or to understand why -something isn't working the way you expect it to, please first use Stack -Overflow or Gitter. -https://stackoverflow.com/questions/tagged/junit5 - -- ( ) **Bug report.** Please provide us the version of JUnit 5 you are -using and, if possible, a failing unit test with your bug report. Don't -forget to describe the rationale for this issue (e.g. expected vs. -actual behavior). - -- ( ) **Feature request.** Start by telling us what problem you’re trying -to solve. Often a solution already exists! Please, don’t send pull requests -to implement new features without first getting our support. - -## Deliverables - -- [ ] ... diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8d847d251231..57a6f10876d6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,11 @@ --- -name: Bug report +name: Report a bug or regression about: Create a report to help us improve - +type: Bug +labels: ["type: bug"] --- - + ## Steps to reproduce diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000000..93fa0b266326 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Ask a question + url: https://github.com/junit-team/junit5/discussions/categories/q-a + about: Please ask and answer questions here + - name: Ask a question (Stack Overflow) + url: https://stackoverflow.com/questions/tagged/junit5 + about: Alternatively, ask questions here diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 24e26904931e..6b6b28087cbb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,8 @@ --- -name: Feature request +name: Request a feature or enhancement about: Suggest an idea for this project - +type: Feature +labels: ["type: new feature"] --- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 23c796b2d20d..000000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Question -about: Please first ask on Gitter or StackOverflow before creating an issue ---- - -This issue tracker is not the place for questions. -If you want to ask how to do something, or to understand why -something isn't working the way you expect it to, please use Gitter [1] or Stack Overflow [2]. - -[1] https://gitter.im/junit-team/junit5 -[2] https://stackoverflow.com/questions/tagged/junit5 diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md new file mode 100644 index 000000000000..fa7b782ac41b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/task.md @@ -0,0 +1,12 @@ +--- +name: Create a task +about: Create a task for a specific piece of work +type: Task +labels: ["type: task"] +--- + + + +## Deliverables + +- [ ] ... diff --git a/.github/actions/main-build/action.yml b/.github/actions/main-build/action.yml index aa6e501bee3c..ecafefb92517 100644 --- a/.github/actions/main-build/action.yml +++ b/.github/actions/main-build/action.yml @@ -4,7 +4,10 @@ inputs: arguments: required: true description: Gradle arguments - default: build + default: :platform-tooling-support-tests:test build --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 + encryptionKey: + required: true + description: Gradle cache encryption key runs: using: "composite" steps: @@ -12,3 +15,9 @@ runs: - uses: ./.github/actions/run-gradle with: arguments: ${{ inputs.arguments }} + encryptionKey: ${{ inputs.encryptionKey }} + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 + if: ${{ always() }} + with: + name: Open Test Reports (${{ github.job }}) + path: '**/build/reports/open-test-report.html' diff --git a/.github/actions/run-gradle/action.yml b/.github/actions/run-gradle/action.yml index 15afa773760b..86436e44afb3 100644 --- a/.github/actions/run-gradle/action.yml +++ b/.github/actions/run-gradle/action.yml @@ -5,23 +5,29 @@ inputs: required: true description: Gradle arguments default: build + encryptionKey: + required: true + description: Gradle cache encryption key runs: using: "composite" steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 id: setup-gradle-jdk with: distribution: temurin java-version: 21 check-latest: true - - uses: gradle/actions/setup-gradle@v3 + - uses: gradle/actions/setup-gradle@94baf225fe0a508e581a564467443d0e2379123b # v4 + with: + cache-encryption-key: ${{ inputs.encryptionKey }} - shell: bash env: JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} run: | ./gradlew \ -Porg.gradle.java.installations.auto-download=false \ - -Pjunit.develocity.predictiveTestSelection.enabled=${{ github.event_name == 'pull_request' }} \ + -Pjunit.develocity.predictiveTestSelection.enabled=true \ + -Pjunit.develocity.predictiveTestSelection.selectRemainingTests=${{ github.event_name != 'pull_request' }} \ "-Dscan.value.GitHub job=${{ github.job }}" \ javaToolchains \ ${{ inputs.arguments }} diff --git a/.github/actions/setup-test-jdk/action.yml b/.github/actions/setup-test-jdk/action.yml index 70a571e59a7f..48b4a3c11e1f 100644 --- a/.github/actions/setup-test-jdk/action.yml +++ b/.github/actions/setup-test-jdk/action.yml @@ -8,7 +8,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-java@v4 + - uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 with: distribution: ${{ inputs.distribution }} java-version: 8 diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 5c19f255d870..000000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: 2 -registries: - gradle-plugin-portal: - type: maven-repository - url: https://plugins.gradle.org/m2 - username: dummy # Required by dependabot - password: dummy # Required by dependabot -updates: - - package-ecosystem: "gradle" - directory: "/" - registries: - - gradle-plugin-portal - schedule: - interval: "weekly" - labels: [ ] - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - labels: [ ] - - package-ecosystem: "github-actions" - directory: "/.github/actions/main-build" - schedule: - interval: "weekly" - labels: [ ] - - package-ecosystem: "github-actions" - directory: "/.github/actions/run-gradle" - schedule: - interval: "weekly" - labels: [ ] - - package-ecosystem: "github-actions" - directory: "/.github/actions/setup-test-jdk" - schedule: - interval: "weekly" - labels: [ ] diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 000000000000..ea024d3d17e6 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,35 @@ +{ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: [ + 'github>junit-team/renovate-config', + ], + packageRules: [ + { + matchCurrentValue: '/^2\\./', + allowedVersions: '(,3.0)', + matchPackageNames: [ + 'org.codehaus.groovy:{/,}**', + ], + }, + { + matchCurrentValue: '/^4\\./', + allowedVersions: '(,5.0)', + matchPackageNames: [ + 'org.apache.groovy:{/,}**', + ], + }, + { + matchCurrentValue: '/^1\\./', + allowedVersions: '/^1\\..*-groovy-2\\.*/', + matchPackageNames: [ + 'org.spockframework:{/,}**', + ], + }, + { + allowedVersions: '!/-SNAPSHOT$/', + matchPackageNames: [ + 'org.opentest4j.reporting:{/,}**', + ], + }, + ], +} diff --git a/gradle/scripts/checkBuildReproducibility.sh b/.github/scripts/checkBuildReproducibility.sh similarity index 79% rename from gradle/scripts/checkBuildReproducibility.sh rename to .github/scripts/checkBuildReproducibility.sh index c434fc778a44..60f2bd165022 100755 --- a/gradle/scripts/checkBuildReproducibility.sh +++ b/.github/scripts/checkBuildReproducibility.sh @@ -1,8 +1,9 @@ #!/bin/bash -e -rm -rf checksums* +rm -f checksums-1.txt checksums-2.txt -export SOURCE_DATE_EPOCH=$(date +%s) +SOURCE_DATE_EPOCH=$(date +%s) +export SOURCE_DATE_EPOCH function calculate_checksums() { OUTPUT=$1 @@ -19,7 +20,7 @@ function calculate_checksums() { | grep '/build/libs/' \ | grep --invert-match 'javadoc' \ | sort \ - | xargs sha256sum > "${OUTPUT}" + | xargs sha512sum > "${OUTPUT}" } diff --git a/.github/scripts/waitForMavenCentralSync.sh b/.github/scripts/waitForMavenCentralSync.sh new file mode 100755 index 000000000000..9a281e56d7ba --- /dev/null +++ b/.github/scripts/waitForMavenCentralSync.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +URL_PATH=$1 +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +"$SCRIPT_DIR"/waitForUrl.sh "https://repo1.maven.org/maven2/$URL_PATH" diff --git a/.github/scripts/waitForUrl.sh b/.github/scripts/waitForUrl.sh new file mode 100755 index 000000000000..e7ae8715c055 --- /dev/null +++ b/.github/scripts/waitForUrl.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +URL=$1 +printf 'Waiting for %s' "$URL" +until curl --output /dev/null --silent --location --head --fail "$URL"; do + printf '.' + sleep 5 +done +echo ' OK' diff --git a/.github/workflows/close-inactive-issues.yml b/.github/workflows/close-inactive-issues.yml index fef88e94d267..a443402a9720 100644 --- a/.github/workflows/close-inactive-issues.yml +++ b/.github/workflows/close-inactive-issues.yml @@ -3,6 +3,7 @@ on: schedule: - cron: "30 1 * * *" workflow_dispatch: +permissions: read-all jobs: close-issues: runs-on: ubuntu-latest @@ -10,7 +11,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v9 + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 with: only-labels: "status: waiting-for-feedback" days-before-stale: 14 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 264bcc0a79f7..24fbbd103c23 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,6 +13,8 @@ on: schedule: - cron: '0 19 * * 3' +permissions: read-all + env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} @@ -30,18 +32,19 @@ jobs: - javascript steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3 with: languages: ${{ matrix.language }} - tools: latest + tools: linked - name: Build uses: ./.github/actions/run-gradle with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | --no-build-cache \ -Dscan.tag.CodeQL \ allMainClasses - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3 diff --git a/.github/workflows/combine-prs.yml b/.github/workflows/combine-prs.yml deleted file mode 100644 index 29df14a19746..000000000000 --- a/.github/workflows/combine-prs.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Combine PRs - -on: - schedule: - - cron: '0 0 * * *' # Every day at 00:00 UTC - workflow_dispatch: - -jobs: - combine-prs: - if: github.repository == 'junit-team/junit5' - runs-on: ubuntu-latest - steps: - - name: combine-prs - uses: github/combine-prs@v5.1.0 - with: - github_token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/cross-version.yml b/.github/workflows/cross-version.yml index 97b19e9ed8b5..534ea6c7b1a8 100644 --- a/.github/workflows/cross-version.yml +++ b/.github/workflows/cross-version.yml @@ -1,13 +1,17 @@ name: Cross-Version on: + schedule: + - cron: '0 0 * * 6' # Every Saturday at 00:00 UTC push: branches: - main - 'releases/**' pull_request: branches: - - '*' + - '**' + +permissions: read-all env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} @@ -18,36 +22,56 @@ jobs: fail-fast: false matrix: jdk: - - version: 22 - version: 23 + type: ga - version: 24 + type: ea - version: 24 + type: ea release: leyden - name: "OpenJDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || 'ea' }})" + - version: 25 + type: ea + name: "OpenJDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }})" runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Set up Test JDK uses: ./.github/actions/setup-test-jdk - name: "Set up JDK ${{ matrix.jdk.version }} (${{ matrix.jdk.release || 'ea' }})" - uses: oracle-actions/setup-java@v1 + if: matrix.jdk.type == 'ea' + uses: oracle-actions/setup-java@2e744f723b003fdd759727d0ff654c8717024845 # v1.4.0 with: website: jdk.java.net release: ${{ matrix.jdk.release || matrix.jdk.version }} version: latest + - name: "Set up JDK ${{ matrix.jdk.version }} (${{ matrix.jdk.distribution || 'temurin' }})" + if: matrix.jdk.type == 'ga' + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 + with: + distribution: ${{ matrix.jdk.distribution || 'temurin' }} + java-version: ${{ matrix.jdk.version }} + check-latest: true - name: 'Prepare JDK${{ matrix.jdk.version }} env var' shell: bash run: echo "JDK${{ matrix.jdk.version }}=$JAVA_HOME" >> $GITHUB_ENV - name: Build uses: ./.github/actions/run-gradle with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | + -Ptesting.enableJaCoCo=false \ -PjavaToolchain.version=${{ matrix.jdk.version }} \ -Dscan.tag.JDK_${{ matrix.jdk.version }} \ - build + build \ + --no-configuration-cache #Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 + if: ${{ always() }} + with: + name: Open Test Reports (${{ github.job }} ${{ matrix.jdk.version }} (${{ matrix.jdk.release || matrix.jdk.type }})) + path: '**/build/reports/open-test-report.html' openj9: strategy: fail-fast: false @@ -57,7 +81,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Set up Test JDK @@ -65,7 +89,7 @@ jobs: with: distribution: semeru - name: 'Set up JDK ${{ matrix.jdk }}' - uses: actions/setup-java@v4 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 with: distribution: semeru java-version: ${{ matrix.jdk }} @@ -76,9 +100,17 @@ jobs: - name: Build uses: ./.github/actions/run-gradle with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | + -Ptesting.enableJaCoCo=false \ -PjavaToolchain.version=${{ matrix.jdk }} \ -PjavaToolchain.implementation=j9 \ -Dscan.tag.JDK_${{ matrix.jdk }} \ -Dscan.tag.OpenJ9 \ - build + build \ + --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 + - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 + if: ${{ always() }} + with: + name: Open Test Reports (${{ github.job }}) + path: '**/build/reports/open-test-report.html' diff --git a/.github/workflows/gradle-dependency-submission.yml b/.github/workflows/gradle-dependency-submission.yml index 32e037d8f4e7..6dff6b23897a 100644 --- a/.github/workflows/gradle-dependency-submission.yml +++ b/.github/workflows/gradle-dependency-submission.yml @@ -5,23 +5,24 @@ on: branches: - main -permissions: - contents: write +permissions: read-all jobs: dependency-submission: if: github.repository == 'junit-team/junit5' runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 with: distribution: temurin java-version: 21 check-latest: true - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@v3 + uses: gradle/actions/dependency-submission@94baf225fe0a508e581a564467443d0e2379123b # v4 diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 76fe77620d7d..000000000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: "Validate Gradle Wrapper" - -on: - push: - branches: - - main - - 'releases/**' - pull_request: - branches: - - '*' - -jobs: - validation: - name: "Validation" - runs-on: ubuntu-latest - steps: - - name: Check out repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v3 diff --git a/.github/workflows/issue-labels.yml b/.github/workflows/issue-labels.yml deleted file mode 100644 index b91cf84c462b..000000000000 --- a/.github/workflows/issue-labels.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Label new issues -on: - issues: - types: - - opened -jobs: - label_issues: - runs-on: ubuntu-latest - permissions: - issues: write - steps: - - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ["status: new"] - }) diff --git a/.github/workflows/label-opened-issues.yml b/.github/workflows/label-opened-issues.yml new file mode 100644 index 000000000000..bbf37c72db6e --- /dev/null +++ b/.github/workflows/label-opened-issues.yml @@ -0,0 +1,30 @@ +name: Add label to opened issues +on: + issues: + types: + - opened +permissions: read-all +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const issue = await github.rest.issues.get({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + const originalLabels = issue.data.labels.map(l => l.name); + const statusLabels = originalLabels.filter(l => l.startsWith("status: ")); + if (statusLabels.length === 0) { + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["status: new"] + }) + } diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0881ae78b4fe..8188d59943f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,9 +5,13 @@ on: branches: - main - 'releases/**' + tags-ignore: + - '**' pull_request: branches: - - '*' + - '**' + +permissions: read-all env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} @@ -15,19 +19,13 @@ env: jobs: Linux: runs-on: ubuntu-latest - permissions: - contents: write # required for submitting a dependency graph steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - - name: Install Graphviz - run: | - sudo apt-get update - sudo apt-get install graphviz - name: Install GraalVM - uses: graalvm/setup-graalvm@v1 + uses: graalvm/setup-graalvm@aafbedb8d382ed0ca6167d3a051415f20c859274 # v1 with: distribution: graalvm-community version: 'latest' @@ -36,44 +34,44 @@ jobs: - name: Build uses: ./.github/actions/main-build with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | - -Ptesting.enableJaCoCo \ + :platform-tooling-support-tests:test \ build \ - jacocoRootReport + 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@v4 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5 with: token: ${{ secrets.CODECOV_TOKEN }} - - name: Run Asciidoctor - uses: ./.github/actions/run-gradle - with: - arguments: | - prepareDocsForUploadToGhPages \ - --no-configuration-cache Windows: runs-on: windows-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Build uses: ./.github/actions/main-build + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} macOS: runs-on: macos-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Build uses: ./.github/actions/main-build + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} publish_artifacts: name: Publish Snapshot Artifacts - needs: linux + needs: macOS runs-on: ubuntu-latest permissions: attestations: write # required for build provenance attestation @@ -81,7 +79,7 @@ jobs: if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Publish @@ -90,38 +88,51 @@ jobs: ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | publish -x check \ - prepareGitHubAttestation \ - --no-configuration-cache + prepareGitHubAttestation - name: Generate build provenance attestations - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: documentation/build/attestation/*.jar - update_documentation: - name: Update Snapshot Documentation + documentation: + name: Build Documentation concurrency: - group: github-pages + group: github-pages-${{ github.ref }} cancel-in-progress: true - needs: Linux + needs: macOS runs-on: ubuntu-latest - if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && github.ref == 'refs/heads/main' steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Install Graphviz run: | sudo apt-get update sudo apt-get install graphviz + - name: Build Documentation + uses: ./.github/actions/run-gradle + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + arguments: | + prepareDocsForUploadToGhPages \ + -Dscan.tag.Documentation + - name: Configure Git + shell: bash + run: | + git config --global user.name "JUnit Team" + git config --global user.email "team@junit.org" - name: Upload Documentation + if: github.event_name == 'push' && github.repository == 'junit-team/junit5' && github.ref == 'refs/heads/main' uses: ./.github/actions/run-gradle with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} arguments: | gitPublishPush \ - -Dscan.tag.Documentation \ - --no-configuration-cache + -Dscan.tag.Documentation env: - GRGIT_USER: ${{ secrets.GH_TOKEN }} + GIT_USERNAME: git + GIT_PASSWORD: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml new file mode 100644 index 000000000000..498e05b2f29e --- /dev/null +++ b/.github/workflows/ossf-scorecard.yml @@ -0,0 +1,62 @@ +name: OpenSSF Scorecard supply-chain security analysis + +on: + branch_protection_rule: + schedule: + - cron: '31 20 * * 6' + push: + branches: [ "main" ] + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + + steps: + - name: "Checkout code" + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.pre.node20 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # 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@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3 + with: + sarif_file: results.sarif diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000000..b1ff8f451505 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,264 @@ +name: Release + +on: + workflow_dispatch: + inputs: + 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") + required: true + +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 + RELEASE_TAG: r${{ github.event.inputs.releaseVersion }} + +jobs: + + verify_reproducibility: + name: Verify reproducibility + runs-on: ubuntu-latest + permissions: + attestations: write # required for build provenance attestation + id-token: write # required for build provenance attestation + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 1 + ref: "refs/tags/${{ env.RELEASE_TAG }}" + - name: Download reference JAR from staging repository + id: referenceJar + run: | + curl --silent --fail --location --output /tmp/reference.jar \ + "${{ 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 + echo "createdBy=$(jq --raw-output .Created_By /tmp/manifest.json)" >> "$GITHUB_OUTPUT" + echo "buildTimestamp=$(jq --raw-output .Build_Date /tmp/manifest.json) $(jq --raw-output .Build_Time /tmp/manifest.json)" >> "$GITHUB_OUTPUT" + - name: Verify artifacts + uses: ./.github/actions/run-gradle + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + arguments: | + --rerun-tasks \ + -Pmanifest.buildTimestamp="${{ steps.referenceJar.outputs.buildTimestamp }}" \ + -Pmanifest.createdBy="${{ steps.referenceJar.outputs.createdBy }}" \ + :verifyArtifactsInStagingRepositoryAreReproducible \ + --remote-repo-url=${{ env.STAGING_REPO_URL }} + - name: Generate build provenance attestations + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 + with: + subject-path: build/repo/**/*.jar + - name: Upload local repository for later jobs + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4 + with: + name: local-maven-repository + path: build/repo + + verify_consumability: + name: Verify consumability + runs-on: ubuntu-latest + steps: + - name: Check out samples repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + repository: ${{ github.repository_owner }}/junit5-samples + token: ${{ secrets.GH_TOKEN }} + fetch-depth: 1 + - name: Set up JDK + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 + with: + java-version: 21 + distribution: temurin + - uses: sbt/setup-sbt@96cf3f09dc501acdad7807fffe97dba9fa0709be # v1 + - name: Update JUnit dependencies in samples + run: java src/Updater.java ${{ github.event.inputs.releaseVersion }} + - name: Inject staging repository URL + run: java src/StagingRepoInjector.java ${{ env.STAGING_REPO_URL }} + - name: Build samples + run: java src/Builder.java + + close_github_milestone: + name: Close GitHub milestone + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Close GitHub milestone + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + result-encoding: string + script: | + const openMilestones = await github.rest.issues.listMilestones({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open' + }); + const [milestone] = openMilestones.data.filter(x => x.title === "${{ github.event.inputs.releaseVersion }}") + if (!milestone) { + throw new Error('Milestone "${{ github.event.inputs.releaseVersion }}" not found'); + } + if (milestone.open_issues > 0) { + throw new Error(`Milestone "${{ github.event.inputs.releaseVersion }}" has ${milestone.open_issues} open issue(s)`); + } + const requestBody = { + owner: context.repo.owner, + repo: context.repo.repo, + milestone_number: milestone.number, + state: 'closed', + due_on: new Date().toISOString() + }; + console.log(requestBody); + await github.rest.issues.updateMilestone(requestBody); + + release_staging_repo: + name: Release staging repo + needs: [ verify_reproducibility, verify_consumability, close_github_milestone ] + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 1 + ref: "refs/tags/${{ env.RELEASE_TAG }}" + - name: Release staging repository + uses: ./.github/actions/run-gradle + env: + ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.SONATYPE_USERNAME }} + ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.SONATYPE_PASSWORD }} + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + arguments: | + releaseSonatypeStagingRepository \ + --staging-repository-id=${{ github.event.inputs.stagingRepoId }} + + publish_documentation: + name: Publish documentation + needs: release_staging_repo + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 1 + ref: "refs/tags/${{ env.RELEASE_TAG }}" + - name: Install Graphviz and Poppler + run: | + sudo apt-get update + sudo apt-get install --yes graphviz poppler-utils + - name: Configure Git + run: | + git config --global user.name "JUnit Team" + git config --global user.email "team@junit.org" + - name: Build and publish documentation + uses: ./.github/actions/run-gradle + env: + GIT_USERNAME: git + GIT_PASSWORD: ${{ secrets.GH_TOKEN }} + with: + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + 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 + id: pagesDeployment + timeout-minutes: 20 + run: | + URL="https://junit.org/junit5/docs/${{ github.event.inputs.releaseVersion }}/user-guide/junit-user-guide-${{ github.event.inputs.releaseVersion }}.pdf" + ./.github/scripts/waitForUrl.sh "$URL" + echo "pdfUrl=$URL" >> "$GITHUB_OUTPUT" + - name: Verify integrity of PDF version of User Guide + 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 + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + fetch-depth: 1 + ref: "refs/tags/${{ env.RELEASE_TAG }}" + - name: Download local Maven repository + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + name: local-maven-repository + path: build/repo + - name: Wait for sync to Maven Central + timeout-minutes: 30 + run: | + find build/repo -name '*.pom' -printf './.github/scripts/waitForMavenCentralSync.sh %P\n' | sh + + update_samples: + name: Update samples + needs: wait_for_maven_central + runs-on: ubuntu-latest + steps: + - name: Check out samples repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + with: + repository: ${{ github.repository_owner }}/junit5-samples + token: ${{ secrets.GH_TOKEN }} + fetch-depth: 1 + - name: Set up JDK + uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 # v4 + with: + java-version: 21 + distribution: temurin + - uses: sbt/setup-sbt@96cf3f09dc501acdad7807fffe97dba9fa0709be # v1 + - name: Update JUnit dependencies in samples + run: java src/Updater.java ${{ github.event.inputs.releaseVersion }} + - name: Build samples + run: java src/Builder.java + - name: Create release branch + run: | + git config user.name "JUnit Team" + git config user.email "team@junit.org" + git switch -c "${{ env.RELEASE_TAG }}" + git status + git commit -a -m "Use ${{ github.event.inputs.releaseVersion }}" + git push origin "${{ env.RELEASE_TAG }}" + - name: Update main branch (only for GA releases) + if: ${{ !contains(github.event.inputs.releaseVersion, '-') }} + run: | + git switch main + git merge --ff-only "${{ env.RELEASE_TAG }}" + git push origin main + + create_github_release: + name: Create GitHub release + needs: wait_for_maven_central + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Create GitHub release + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const releaseVersion = "${{ github.event.inputs.releaseVersion }}"; + const jupiterVersion = releaseVersion; + const vintageVersion = releaseVersion; + const platformVersion = "1." + releaseVersion.substring(2); + const requestBody = { + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: `r${releaseVersion}`, + name: `JUnit ${releaseVersion}`, + generate_release_notes: true, + body: `JUnit ${jupiterVersion} = Platform ${platformVersion} + Jupiter ${jupiterVersion} + Vintage ${vintageVersion}\n\nSee [Release Notes](https://junit.org/junit5/docs/${releaseVersion}/release-notes/).`, + prerelease: releaseVersion.includes("-"), + }; + console.log(requestBody); + await github.rest.repos.createRelease(requestBody); diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml index 5866318d1356..6d5f3bb7a1cf 100644 --- a/.github/workflows/reproducible-build.yml +++ b/.github/workflows/reproducible-build.yml @@ -7,7 +7,9 @@ on: - 'releases/**' pull_request: branches: - - '*' + - '**' + +permissions: read-all env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} @@ -18,14 +20,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: fetch-depth: 1 - name: Restore Gradle cache and display toolchains uses: ./.github/actions/run-gradle with: - arguments: --quiet + encryptionKey: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + arguments: | + --quiet \ + --no-configuration-cache # Disable configuration cache due to https://github.com/diffplug/spotless/issues/2318 - name: Build and compare checksums shell: bash - run: | - ./gradle/scripts/checkBuildReproducibility.sh + run: ./.github/scripts/checkBuildReproducibility.sh diff --git a/.github/workflows/sanitize-closed-issues.yml b/.github/workflows/sanitize-closed-issues.yml new file mode 100644 index 000000000000..6f0721a0d2db --- /dev/null +++ b/.github/workflows/sanitize-closed-issues.yml @@ -0,0 +1,70 @@ +name: Sanitizes assigned labels and milestone on closed issues +on: + issues: + types: + - closed +permissions: read-all +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + with: + script: | + const issue = await github.rest.issues.get({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }); + const originalLabels = issue.data.labels.map(l => l.name); + const newLabels = originalLabels.filter(l => l !== "status: in progress" && l !== "status: new"); + if (newLabels.length !== originalLabels.length) { + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: newLabels, + }); + } + if (issue.data.state_reason === "not_planned") { + if (issue.data.milestone) { + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + milestone: null, + }); + } + const statusLabels = newLabels.filter(l => l.startsWith("status: ")); + if (statusLabels.length === 0) { + await github.rest.issues.createComment({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Please assign a status label to this issue.", + }); + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + }); + } + } else { + if (!(newLabels.includes("type: task") || newLabels.includes("type: question")) && !issue.data.milestone) { + await github.rest.issues.createComment({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: "Please assign a milestone to this issue or label it with `type: task` or `type: question`.", + }); + await github.rest.issues.update({ + issue_number: issue.data.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + }); + } + } diff --git a/.gitignore b/.gitignore index 2764c9175b99..612c29c09076 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Gradle .gradle +.kotlin build # Ignore Gradle GUI config @@ -30,5 +31,6 @@ gradle-app.setting coverage.db* .metadata /.sdkmanrc +/.tool-versions checksums* diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index e6094d409c45..9f10a217b5b9 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -9,10 +9,10 @@ - - + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 081a337257e9..135a6cd3456c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,14 @@ # Contributing +## Getting Started + +We welcome new contributors to the project! +If you're interested, please check for [issues labeled with `up-for-grabs` +that are not yet in progress](https://github.com/junit-team/junit5/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3Aup-for-grabs%20-label%3A%22status%3A%20in%20progress%22). +Generally, before you work on an issue, post a comment and ask whether it can be started. +Please wait for the core team to respond and assign the issue to you before making any code +changes. + ## JUnit Contributor License Agreement - You will only Submit Contributions where You have authored 100% of the content. diff --git a/LICENSE-notice.md b/NOTICE.md similarity index 100% rename from LICENSE-notice.md rename to NOTICE.md diff --git a/README.md b/README.md index d817c1c29b23..0f4d7b3373b9 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ This repository is the home of _JUnit 5_. * **Gold Sponsors:** [JetBrains](https://jb.gg/junit-logo) * **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/) +* **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/) ## Latest Releases -- General Availability (GA): [JUnit 5.10.3](https://github.com/junit-team/junit5/releases/tag/r5.10.3) (June 27, 2024) -- Preview (Milestone/Release Candidate): [JUnit 5.11.0-M2](https://github.com/junit-team/junit5/releases/tag/r5.11.0-M2) (May 17, 2024) +- General Availability (GA): [JUnit 5.11.4](https://github.com/junit-team/junit5/releases/tag/r5.11.4) (December 16, 2024) +- Preview (Milestone/Release Candidate): [JUnit 5.12.0-M1](https://github.com/junit-team/junit5/releases/tag/r5.12.0-M1) (January 31, 2025) ## Documentation @@ -49,7 +49,7 @@ builds of the next OpenJDK. Code coverage using [JaCoCo] for the latest build is available on [Codecov]. A code coverage report can also be generated locally via the [Gradle Wrapper] by -executing `./gradlew -Ptesting.enableJaCoCo clean jacocoRootReport`. The results will be available +executing `./gradlew clean jacocoRootReport`. The results will be available in `build/reports/jacoco/jacocoRootReport/html/index.html`. ## Develocity diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 000000000000..641960cd0f8f --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,41 @@ +# Releasing + +## Pre-release steps + +- [ ] Switch or create the release branch for this feature release (e.g. `releases/5.12.x`) +- [ ] Change `version`, `platformVersion`, and `vintageVersion` in `gradle.properties` to the versions about to be released +- [ ] 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` +- [ ] Tag current commit: `git tag -s -m ${VERSION} r${VERSION}` +- [ ] 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 $(git rev-parse --abbrev-ref HEAD) -f releaseVersion=${VERSION} -f stagingRepoId=orgjunit-1234 release.yml` + - Select the release branch + - Enter the version to be released + - Enter the staging repository ID from the output of above Gradle build + +## Post-release steps + +- [ ] 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 and push to GitHub +- [ ] Post about the new release: + - [ ] [Mastodon](https://fosstodon.org/@junit) + - [ ] [Bluesky](https://bsky.app/profile/junit.org) + +### Preview releases (milestones and release candidates) + +- [ ] Fast-forward merge the release branch to `main` and push to GitHub +- [ ] Create release notes for the next preview or feature release from the template + +### Feature releases (5.x.0) + +- [ ] Fast-forward merge the release branch to `main` and push to GitHub +- [ ] Update the [security policy](https://github.com/junit-team/junit5/blob/main/SECURITY.md) and commit with message "Update security policy to reflect 5.x release" or similar +- [ ] Create release notes for the next feature release from the template +- [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json) + +### Patch releases (5.x.y) + +- [ ] Cherry-pick the tagged commit from the release branch to `main` and resolve the conflict in `gradle.properties` by choosing the version of the `main` branch +- [ ] Include the release notes of the patch release on `main` if not already present +- [ ] Update [JBang catalog](https://github.com/junit-team/jbang-catalog/blob/main/jbang-catalog.json) diff --git a/SECURITY.md b/SECURITY.md index cb9359153b13..566ad1ebb323 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,12 +1,14 @@ # Security Policy +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9607/badge)](https://www.bestpractices.dev/projects/9607) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/junit-team/junit5/badge)](https://scorecard.dev/viewer/?uri=github.com/junit-team/junit5) + ## Supported Versions -| Version | Supported | -| -------- | ------------------ | -| 5.10.x | :white_check_mark: | -| < 5.10 | :x: | +| Version | Supported | +|---------| ------------------ | +| 5.11.x | :white_check_mark: | +| < 5.11 | :x: | ## Reporting a Vulnerability -To report a security vulnerability, please send an email to security@junit.org. +To report a security vulnerability, please send an email to security@junit.org. You can use the [published OpenPGP key](https://keys.openpgp.org/search?q=security%40junit.org) with fingerprint `0152DA30EABC7ABADCB09D10D9A6B1329D191D25` to encrypt the message body. diff --git a/build.gradle.kts b/build.gradle.kts index 9ee161e48159..cd79a120700b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ plugins { - alias(libs.plugins.nohttp) 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.temp-maven-repo") @@ -30,7 +30,7 @@ val platformProjects by extra(listOf( projects.junitPlatformSuiteCommons, projects.junitPlatformSuiteEngine, projects.junitPlatformTestkit -).map { it.dependencyProject }) +).map { dependencyProject(it) }) val jupiterProjects by extra(listOf( projects.junitJupiter, @@ -38,20 +38,21 @@ val jupiterProjects by extra(listOf( projects.junitJupiterEngine, projects.junitJupiterMigrationsupport, projects.junitJupiterParams -).map { it.dependencyProject }) +).map { dependencyProject(it) }) val vintageProjects by extra(listOf( - projects.junitVintageEngine.dependencyProject + dependencyProject(projects.junitVintageEngine) )) val mavenizedProjects by extra(platformProjects + jupiterProjects + vintageProjects) -val modularProjects by extra(mavenizedProjects - listOf(projects.junitPlatformConsoleStandalone.dependencyProject)) +val modularProjects by extra(mavenizedProjects - setOf(dependencyProject(projects.junitPlatformConsoleStandalone))) dependencies { modularProjects.forEach { jacocoAggregation(it) } jacocoAggregation(projects.documentation) + jacocoAggregation(projects.jupiterTests) jacocoAggregation(projects.platformTests) } @@ -61,11 +62,3 @@ nexusPublishing { sonatype() } } - -nohttp { - source.exclude("**/.gradle/**", "gradle/plugins/**/build/**", "buildSrc/build/**") -} - -tasks.checkstyleNohttp { - notCompatibleWithConfigurationCache("https://github.com/spring-io/nohttp/issues/61") -} diff --git a/documentation/documentation.gradle.kts b/documentation/documentation.gradle.kts index 2f8bdb819773..02c96f3035fc 100644 --- a/documentation/documentation.gradle.kts +++ b/documentation/documentation.gradle.kts @@ -6,6 +6,7 @@ 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.jetbrains.kotlin.incremental.deleteRecursivelyOrThrow plugins { alias(libs.plugins.asciidoctorConvert) @@ -13,6 +14,7 @@ plugins { alias(libs.plugins.gitPublish) alias(libs.plugins.plantuml) id("junitbuild.build-parameters") + id("junitbuild.java-multi-release-test-sources") id("junitbuild.kotlin-library-conventions") id("junitbuild.testing-conventions") } @@ -91,22 +93,27 @@ asciidoctorj { } val buildRevision: String by rootProject.extra -val snapshot = rootProject.version.toString().contains("SNAPSHOT") -val docsVersion = if (snapshot) "snapshot" else rootProject.version -val releaseBranch = if (snapshot) "HEAD" else "r${rootProject.version}" +val snapshot = version.isSnapshot() +val docsVersion = if (snapshot) "snapshot" else version +val releaseBranch = if (snapshot) "HEAD" else "r${version}" val docsDir = layout.buildDirectory.dir("ghpages-docs") val replaceCurrentDocs = buildParameters.documentation.replaceCurrentDocs val uploadPdfs = !snapshot -val userGuidePdfFileName = "junit-user-guide-${rootProject.version}.pdf" -val ota4jDocVersion = if (libs.versions.opentest4j.get().contains("SNAPSHOT")) "snapshot" else libs.versions.opentest4j.get() -val apiGuardianDocVersion = if (libs.versions.apiguardian.get().contains("SNAPSHOT")) "snapshot" else libs.versions.apiguardian.get() +val userGuidePdfFileName = "junit-user-guide-${version}.pdf" +val ota4jDocVersion = libs.versions.opentest4j.map { if (it.isSnapshot()) "snapshot" else it }.get() +val apiGuardianDocVersion = libs.versions.apiguardian.map { if (it.isSnapshot()) "snapshot" else it }.get() gitPublish { repoUri = "https://github.com/junit-team/junit5.git" + referenceRepoUri = rootDir.toURI().toString() + branch = "gh-pages" sign = false fetchDepth = 1 + username = providers.environmentVariable("GIT_USERNAME") + password = providers.environmentVariable("GIT_PASSWORD") + contents { from(docsDir) into("docs") @@ -143,23 +150,40 @@ require(externalModulesWithoutModularJavadoc.values.all { it.endsWith("/") }) { tasks { + val consoleLauncherTestReportsDir = project.layout.buildDirectory.dir("console-launcher-test-results") + val consoleLauncherTestEventXmlFiles = + files(consoleLauncherTestReportsDir.map { it.asFileTree.matching { include("**/open-test-report.xml") } }) + val consoleLauncherTest by registering(RunConsoleLauncher::class) { args.addAll("execute") args.addAll("--scan-classpath") args.addAll("--config=junit.platform.reporting.open.xml.enabled=true") - val reportsDir = project.layout.buildDirectory.dir("console-launcher-test-results") - outputs.dir(reportsDir) + args.addAll("--config=junit.platform.output.capture.stdout=true") + args.addAll("--config=junit.platform.output.capture.stderr=true") + outputs.dir(consoleLauncherTestReportsDir) argumentProviders.add(CommandLineArgumentProvider { listOf( - "--reports-dir=${reportsDir.get()}", - "--config=junit.platform.reporting.output.dir=${reportsDir.get()}" - + "--reports-dir=${consoleLauncherTestReportsDir.get()}", ) }) args.addAll("--include-classname", ".*Tests") args.addAll("--include-classname", ".*Demo") args.addAll("--exclude-tag", "exclude") args.addAll("--exclude-tag", "timeout") + + doFirst { + consoleLauncherTestReportsDir.get().asFile.deleteRecursivelyOrThrow() + } + + finalizedBy(generateOpenTestHtmlReport) + } + + generateOpenTestHtmlReport { + mustRunAfter(consoleLauncherTest) + inputs.files(consoleLauncherTestEventXmlFiles).withPathSensitivity(RELATIVE).skipWhenEmpty() + argumentProviders += CommandLineArgumentProvider { + consoleLauncherTestEventXmlFiles.files.map { it.absolutePath }.toList() + } } register("consoleLauncher") { @@ -175,6 +199,10 @@ tasks { } } + testRelease21 { + include("**/*Demo.class") + } + check { dependsOn(consoleLauncherTest) } @@ -313,6 +341,13 @@ tasks { inputs.dir(kotlin.srcDirs.first()) } + sourceSets["testRelease21"].apply { + attributes(mapOf( + "testRelease21Dir" to java.srcDirs.first() + )) + inputs.dir(java.srcDirs.first()) + } + jvm { // To avoid warning, see https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/597 jvmArgs( @@ -469,9 +504,6 @@ tasks { dependsOn(fixJavadoc, asciidoctor, asciidoctorPdf) outputs.dir(docsDir) - from(layout.buildDirectory.dir("checksum")) { - include("published-checksum.txt") - } from(asciidoctor.map { it.outputDir }) { include("user-guide/**") include("release-notes/**") @@ -526,14 +558,14 @@ tasks { eclipse { classpath { - plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowedClasspath"]) - plusConfigurations.add(projects.junitJupiterParams.dependencyProject.configurations["shadowedClasspath"]) + plusConfigurations.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) + plusConfigurations.add(dependencyProject(projects.junitJupiterParams).configurations["shadowedClasspath"]) } } idea { module { - scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowedClasspath"]) - scopes["PROVIDED"]!!["plus"]!!.add(projects.junitJupiterParams.dependencyProject.configurations["shadowedClasspath"]) + scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) + scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitJupiterParams).configurations["shadowedClasspath"]) } } diff --git a/documentation/src/docs/asciidoc/link-attributes.adoc b/documentation/src/docs/asciidoc/link-attributes.adoc index dfbe7932d479..4010e54b7085 100644 --- a/documentation/src/docs/asciidoc/link-attributes.adoc +++ b/documentation/src/docs/asciidoc/link-attributes.adoc @@ -40,6 +40,7 @@ endif::[] :DiscoverySelectors_selectPackage: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectPackage(java.lang.String)[selectPackage] :DiscoverySelectors_selectUniqueId: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUniqueId(java.lang.String)[selectUniqueId] :DiscoverySelectors_selectUri: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/DiscoverySelectors.html#selectUri(java.lang.String)[selectUri] +:EngineDiscoveryRequest: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/EngineDiscoveryRequest.html[EngineDiscoveryRequest] :FileSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/FileSelector.html[FileSelector] :HierarchicalTestEngine: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.html[HierarchicalTestEngine] :IterationSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/IterationSelector.html[IterationSelector] @@ -47,6 +48,7 @@ endif::[] :ModuleSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/ModuleSelector.html[ModuleSelector] :NestedClassSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedClassSelector.html[NestedClassSelector] :NestedMethodSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/NestedMethodSelector.html[NestedMethodSelector] +:OutputDirectoryProvider: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/reporting/OutputDirectoryProvider.html[OutputDirectoryProvider] :PackageSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/PackageSelector.html[PackageSelector] :ParallelExecutionConfigurationStrategy: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.html[ParallelExecutionConfigurationStrategy] :UniqueIdSelector: {javadoc-root}/org.junit.platform.engine/org/junit/platform/engine/discovery/UniqueIdSelector.html[UniqueIdSelector] @@ -128,6 +130,8 @@ endif::[] :Execution: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Execution.html[@Execution] :Isolated: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Isolated.html[@Isolated] :ResourceLock: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLock.html[@ResourceLock] +:ResourceLockTarget: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLockTarget.html[ResourceLockTarget] +:ResourceLocksProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/ResourceLocksProvider.html[ResourceLocksProvider] :Resources: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/parallel/Resources.html[Resources] // Jupiter Extension APIs :extension-api-package: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/package-summary.html[org.junit.jupiter.api.extension] @@ -155,6 +159,7 @@ endif::[] :TestTemplateInvocationContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContext.html[TestTemplateInvocationContext] :TestTemplateInvocationContextProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.html[TestTemplateInvocationContextProvider] :TestWatcher: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/TestWatcher.html[TestWatcher] +:PreInterruptCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/PreInterruptCallback.html[PreInterruptCallback] // Jupiter Conditions :DisabledForJreRange: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledForJreRange.html[@DisabledForJreRange] :DisabledIf: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/condition/DisabledIf.html[@DisabledIf] diff --git a/documentation/src/docs/asciidoc/release-notes/index.adoc b/documentation/src/docs/asciidoc/release-notes/index.adoc index bd8baf6f8c31..eaefaaf31979 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.10 GA. +This document contains the _change log_ for all JUnit 5 releases since 5.11 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,14 +17,16 @@ authors as well as build tool and IDE vendors. include::{includedir}/link-attributes.adoc[] -include::{basedir}/release-notes-5.11.0-RC1.adoc[] +include::{basedir}/release-notes-5.12.0-RC1.adoc[] -include::{basedir}/release-notes-5.11.0-M2.adoc[] +include::{basedir}/release-notes-5.12.0-M1.adoc[] -include::{basedir}/release-notes-5.11.0-M1.adoc[] +include::{basedir}/release-notes-5.11.4.adoc[] -include::{basedir}/release-notes-5.10.2.adoc[] +include::{basedir}/release-notes-5.11.3.adoc[] -include::{basedir}/release-notes-5.10.1.adoc[] +include::{basedir}/release-notes-5.11.2.adoc[] -include::{basedir}/release-notes-5.10.0.adoc[] +include::{basedir}/release-notes-5.11.1.adoc[] + +include::{basedir}/release-notes-5.11.0.adoc[] diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0.adoc deleted file mode 100644 index 1c3d7952307f..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0.adoc +++ /dev/null @@ -1,25 +0,0 @@ -[[release-notes-5.10.0]] -== 5.10.0 - -*Date of Release:* July 23, 2023 - -*Scope:* - -* Promotion of various experimental APIs to stable -* New `LauncherInterceptor` SPI -* New `testfeed` details mode for `ConsoleLauncher` -* New `ConsoleLauncher` subcommand for test discovery without execution -* Dry-run mode for test execution -* New `NamespacedHierarchicalStore` for use in third-party test engines -* Stacktrace pruning to hide internal JUnit calls -* New `@SelectMethod` support in test `@Suite` classes. -* New `TempDirFactory` SPI for customizing how temporary directories are created -* Failure threshold for `@RepeatedTest` -* New convenience base classes for implementing `ArgumentsProvider` and `ArgumentConverter` -* Custom class loader support for class/method selectors, `@MethodSource`, `@EnabledIf`, - and `@DisabledIf` -* Improved configurability of parallel execution -* Numerous bug fixes and minor improvements - -For complete details consult the -https://junit.org/junit5/docs/5.10.0/release-notes/index.html[5.10.0 Release Notes] online. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc deleted file mode 100644 index af0ac43916b4..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc +++ /dev/null @@ -1,60 +0,0 @@ -[[release-notes-5.10.1]] -== 5.10.1 - -*Date of Release:* November 5, 2023 - -*Scope:* minor bug fixes and improvements since 5.10.0. - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/72?closed=1+[5.10.1] milestone page in the -JUnit repository on GitHub. - - -[[release-notes-5.10.1-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* Field predicates are now applied while searching the type hierarchy. This fixes bugs in - `findFields(...)` and `streamFields(...)` in `ReflectionSupport` as well as - `findAnnotatedFields(...)` and `findAnnotatedFieldValues(...)` in `AnnotationSupport`. - - See link:https://github.com/junit-team/junit5/issues/3532[issue 3532] for details. -* Method predicates are now applied while searching the type hierarchy. This fixes bugs - in `findMethods(...)` and `streamMethods(...)` in `ReflectionSupport` as well as - `findAnnotatedMethods(...)` in `AnnotationSupport`. - - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. - - -[[release-notes-5.10.1-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* A package-private static field annotated with `@TempDir` is no longer _shadowed_ by a - non-static field annotated with `@TempDir` when the non-static field resides in a - different package and has the same name as the static field. - - See link:https://github.com/junit-team/junit5/issues/3532[issue 3532] for details. -* A package-private class-level lifecycle method annotated with `@BeforeAll` or - `@AfterAll` is no longer _shadowed_ by a method-level lifecycle method annotated with - `@BeforeEach` or `@AfterEach` when the method-level lifecycle method resides in a - different package and has the same name as the class-level lifecycle method. - - See link:https://github.com/junit-team/junit5/issues/3498[issue 3498] for details. -* The `ON_SUCCESS` cleanup mode of `@TempDir` now takes into account failures of test - methods and nested tests when it's declared on the class level, e.g. as a static field. -* The `RandomNumberExtension` example in the - <<../user-guide/index.adoc#extensions-RandomNumberExtension, User Guide>> has been - updated to properly support `Integer` types as well as non-static field injection. - -==== New Features and Improvements - -* Improved Javadoc for `Assertions.assertTimeoutPreemptively` regarding thread interrupt. -* Documentation for `@Disabled` and conditional annotations now explicitly explains that - such annotations are not inherited by subclasses. - - -[[release-notes-5.10.1-junit-vintage]] -=== JUnit Vintage - -==== Bug Fixes - -* Fixed reporting for JUnit 3 test classes that use JUnit 4's `@Ignored` annotation. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.2.adoc deleted file mode 100644 index 1558506cb03c..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.2.adoc +++ /dev/null @@ -1,63 +0,0 @@ -[[release-notes-5.10.2]] -== 5.10.2 - -*Date of Release:* February 4, 2024 - -*Scope:* minor bug fixes and changes since 5.10.1. - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/73?closed=1+[5.10.2] milestone page in the JUnit repository -on GitHub. - - -[[release-notes-5.10.2-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* The `junit-platform-launcher` may now be used as a Java module when - `junit.platform.launcher.interceptors.enabled` is set to `true`. - - See issue link:https://github.com/junit-team/junit5/issues/3561[#3561] for details. - -==== Deprecations and Breaking Changes - -* Field predicates are no longer applied eagerly while searching the type hierarchy. - - This reverts changes made in 5.10.1 that affected `findFields(...)` and - `streamFields(...)` in `ReflectionSupport` as well as `findAnnotatedFields(...)` and - `findAnnotatedFieldValues(...)` in `AnnotationSupport`. - - See issue link:https://github.com/junit-team/junit5/issues/3638[#3638] for details. -* Method predicates are no longer applied eagerly while searching the type hierarchy. - - This reverts changes made in 5.10.1 that affected `findMethods(...)` and - `streamMethods(...)` in `ReflectionSupport` as well as `findAnnotatedMethods(...)` in - `AnnotationSupport`. - - See issue link:https://github.com/junit-team/junit5/issues/3600[#3600] for details. - - -[[release-notes-5.10.2-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* JUnit Jupiter once again properly detects when a `@Test` method is overridden in a - subclass. - - See issue link:https://github.com/junit-team/junit5/issues/3600[#3600] for details. - -==== Deprecations and Breaking Changes - -* A package-private static field annotated with `@TempDir` is once again _shadowed_ by a - non-static field annotated with `@TempDir` when the non-static field resides in a - different package and has the same name as the static field. - - This reverts changes made in 5.10.1. - - See issue link:https://github.com/junit-team/junit5/issues/3638[#3638] for details. -* A package-private class-level lifecycle method annotated with `@BeforeAll` or - `@AfterAll` is once again _shadowed_ by a method-level lifecycle method annotated with - `@BeforeEach` or `@AfterEach` when the method-level lifecycle method resides in a - different package and has the same name as the class-level lifecycle method. - - This reverts changes made in 5.10.1. - - See issue link:https://github.com/junit-team/junit5/issues/3600[#3600] for details. - - -[[release-notes-5.10.2-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.3.adoc deleted file mode 100644 index f98b8fd062f7..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.3.adoc +++ /dev/null @@ -1,41 +0,0 @@ -[[release-notes-5.10.3]] -== 5.10.3 - -*Date of Release:* June 27, 2024 - -*Scope:* Bug fixes and enhancements since 5.10.2 - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/78?closed=1+[5.10.3] milestone page in the JUnit repository -on GitHub. - - -[[release-notes-5.10.3-junit-platform]] -=== JUnit Platform - -==== Bug Fixes - -* The `junit-platform-suite-engine` now includes configuration provided via - `@ConfigurationParameter` when selecting tests by `UniqueId`. -* In order to support using `@EnabledInNativeImage` on test classes, - `UniqueIdTrackingListener` now tracks descendants of skipped test containers. -* Attempting to deserialize a `TestIdentifier` no longer causes a `NullPointerException` - when there is no parent identifier. See - link:https://github.com/junit-team/junit5/issues/3819[issue 3819]. - - -[[release-notes-5.10.3-junit-jupiter]] -=== JUnit Jupiter - -==== Bug Fixes - -* `TempDir` suppresses `NoSuchFileException` when deleting files that may have been deleted - by another thread or process. -* `MethodOrderer.Random` and `ClassOrderer.Random` now use the same default seed that is - generated during class initialization. - - -[[release-notes-5.10.3-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc deleted file mode 100644 index 8009feae3f37..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M1.adoc +++ /dev/null @@ -1,120 +0,0 @@ -[[release-notes-5.11.0-M1]] -== 5.11.0-M1 - -*Date of Release:* April 23, 2024 - -*Scope:* - -* Numerous bug fixes and enhancements regarding field and method search algorithms -* `@FieldSource` annotation for use with `@ParameterizedTest` methods -* `@AutoClose` annotation to automatically close field resources in tests -* `ConversionSupport` utility for converting from a string to a supported target type -* Various documentation improvements - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/68?closed=1+[5.11.0-M1] milestone page in the JUnit -repository on GitHub. - -For assistance with upgrading, please consult the -link:https://github.com/junit-team/junit5/wiki/Upgrading-to-JUnit-5.11[Upgrading to JUnit 5.11] -page in the Wiki. - - -[[release-notes-5.11.0-M1-junit-platform]] -=== JUnit Platform - -[[release-notes-5.11.0-M1-junit-platform-bug-fixes]] -==== Bug Fixes - -* Field and method search algorithms now adhere to standard Java semantics regarding - whether a given field or method is visible or overridden according to the rules of the - Java language. See the new - <<../user-guide/index.adoc#extensions-supported-utilities-search-semantics, Field and - Method Search Semantics>> section of the User Guide for details. -* `ReflectionSupport.findFields(...)` now returns a distinct set of fields. -* Fixed parsing of recursive jar URIs which allows the JUnit Platform Launcher to be used - inside Spring Boot executable jars for Spring Boot 3.2 and later. -* The `junit-platform-suite-engine` now includes configuration provided via - `@ConfigurationParameter` when selecting tests by `UniqueId` (backported to 5.10.3). -* In order to support using `@EnabledInNativeImage` on test classes, - `UniqueIdTrackingListener` now tracks descendants of skipped test containers (backported - to 5.10.3). - -[[release-notes-5.11.0-M1-junit-platform-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* As mentioned in the _Bug Fixes_ section above, field and method search algorithms now - adhere to standard Java semantics regarding whether a given field or method is visible - or overridden according to the rules of the Java language. The changes in the search - algorithms may, however, result in breaking changes for some use cases. In light of - that, it is possible to revert to the previous "legacy semantics". See the new - <<../user-guide/index.adoc#extensions-supported-utilities-search-semantics, Field and - Method Search Semantics>> section of the User Guide for details. - -[[release-notes-5.11.0-M1-junit-platform-new-features-and-improvements]] -==== New Features and Improvements - -* New `ConversionSupport` utility in `junit-platform-commons` which exposes the conversion - logic that was previously private to JUnit Jupiter's `@ParameterizedTest` infrastructure - -- for use in third-party extensions and test engines. -* Error messages for type mismatches in `NamespacedHierarchicalStore` now include the - actual type and value in addition to the required type. -* Updated `open-test-reporting` dependency to `0.1.0-M2`. - - -[[release-notes-5.11.0-M1-junit-jupiter]] -=== JUnit Jupiter - -[[release-notes-5.11.0-M1-junit-jupiter-bug-fixes]] -==== Bug Fixes - -* Due to changes in the JUnit Platform regarding field and method search algorithms (see - <> above), numerous bugs have been - addressed within JUnit Jupiter, including but not limited to the following. - ** Two `@TempDir` fields with the same name in a superclass and subclass will now both - be injected. - ** Two `@Test` methods with the same signature in a superclass and subclass will now - both be invoked, as long as the `@Test` method in the subclass does not override the - `@Test` method in the superclass, which can occur if the superclass method is `private` - or if the superclass method is package-private and resides in a different package than - the subclass. - *** The same applies to other types of test methods (`@TestFactory`, - `@ParameterizedTest`, etc.) as well as lifecycle methods (`@BeforeAll`, - `@AfterAll`, `@BeforeEach`, and `@AfterEach`). - -[[release-notes-5.11.0-M1-junit-jupiter-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* Kotlin support now depends on Kotlin API and language version 1.6; whereas, it - previously depended on version 1.3. - -[[release-notes-5.11.0-M1-junit-jupiter-new-features-and-improvements]] -==== New Features and Improvements - -* New `@FieldSource` annotation for use with `@ParameterizedTest` methods which allows - you to source arguments from a local field or an external field referenced by - fully qualified field name. This feature is similar to the existing `@MethodSource` - feature. See the - <<../user-guide/index.adoc#writing-tests-parameterized-tests-sources-FieldSource, User - Guide>> for details. -* New `@AutoClose` annotation that can be applied to fields within tests to automatically - close the annotated resource after test execution. See the - <<../user-guide/index.adoc#writing-tests-built-in-extensions-AutoClose, User Guide>> for - details. -* `@TempDir` now suppresses `NoSuchFileException` when attempting to delete files that may - have been already deleted by another thread or process. -* `JAVA_23` has been added to the `JRE` enum for use with JRE-based execution conditions. -* New <<../user-guide/index.adoc#writing-tests-exceptions, Exception Handling>> - documentation in the User Guide. -* Improved documentation for <<../user-guide/index.adoc#writing-tests-assumptions, - Assumptions>> in the User Guide. -* Improved Javadoc for `assertThrows()` and `assertThrowsExactly()` to make it clear that - the supplied message is not the _expected message_ of the thrown exception. -* Improved documentation for semantics of a disabled test regarding class-level lifecycle - methods and callbacks. - - -[[release-notes-5.11.0-M1-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc deleted file mode 100644 index 220962cb1863..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc +++ /dev/null @@ -1,84 +0,0 @@ -[[release-notes-5.11.0-M2]] -== 5.11.0-M2 - -*Date of Release:* May 17, 2024 - -*Scope:* - -* Repeatable `@..Source` annotations for parameterized tests -* Extensible syntax for specifying discovery selectors -* Console Launcher `--version` option -* Improvements to `NamespacedHierarchicalStore` -* Bug fixes - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/74?closed=1+[5.11.0-M2] milestone page in the JUnit -repository on GitHub. - - -[[release-notes-5.11.0-M2-overall-improvements]] -=== Overall Improvements - -[[release-notes-5.11.0-M2-overall-new-features-and-improvements]] -==== New Features and Improvements - -* Java classes in published artifacts are now compiled with the `-parameters` option of - `javac` and thus now contain metadata for reflection on parameters such as their names. - - -[[release-notes-5.11.0-M2-junit-platform]] -=== JUnit Platform - -[[release-notes-5.11.0-M2-junit-platform-bug-fixes]] -==== Bug Fixes - -* Attempting to deserialize a `TestIdentifier` no longer causes a `NullPointerException` - when there is no parent identifier. See - link:https://github.com/junit-team/junit5/issues/3819[issue 3819] (backported to - 5.10.3). - -[[release-notes-5.11.0-M2-junit-platform-new-features-and-improvements]] -==== New Features and Improvements - -* All Platform implementations of `DiscoverySelector` now have a parseable string - representation that can be generated by calling the new - `DiscoverySelector.toIdentifier()` method and `toString()` on the returned - `DiscoverySelectorIdentifier`. This string representation can be used to reconstruct - the original `DiscoverySelector` by calling the new `DiscoverySelectors.parse()` method. - This change will allow build tools and IDEs to provide generic mechanisms for specifying - selectors on the command line or in configuration files without having to support each - selector type individually. - - The Console Launcher supports specifying selectors via their identifiers using the - `--select` option. For example, `--select class:foo.Bar` will run all tests in the - `foo.Bar` class. - - Similarly, the JUnit Platform Suite engine provides a new `@Select(")` - annotation. -* The Console Launcher now provides a `--version` option. -* `NamespacedHierarchicalStore` now throws an `IllegalStateException` for any attempt to - modify or query the store after it has been closed. In addition, an attempt to close a - store that has already been closed will have no effect. - - See link:https://github.com/junit-team/junit5/issues/3614[issue 3614] for details. - - -[[release-notes-5.11.0-M2-junit-jupiter]] -=== JUnit Jupiter - -[[release-notes-5.11.0-M2-junit-jupiter-bug-fixes]] -==== Bug Fixes - -* `MethodOrderer.Random` and `ClassOrderer.Random` now use the same default seed that is - generated during class initialization (backported to 5.10.3). - -[[release-notes-5.11.0-M2-junit-jupiter-new-features-and-improvements]] -==== New Features and Improvements - -* `@..Source` annotations for parameterized tests can now be used as repeatable - annotations. See the - <<../user-guide/index.adoc#writing-tests-parameterized-repeatable-sources, User Guide>> - for details. - - -[[release-notes-5.11.0-M2-junit-vintage]] -=== JUnit Vintage - -No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc deleted file mode 100644 index e76d8c4020aa..000000000000 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc +++ /dev/null @@ -1,82 +0,0 @@ -[[release-notes-5.11.0-RC1]] -== 5.11.0-RC1 - -*Date of Release:* ❓ - -*Scope:* ❓ - -For a complete list of all _closed_ issues and pull requests for this release, consult the -link:{junit5-repo}+/milestone/77?closed=1+[5.11.0-RC1] milestone page in the JUnit -repository on GitHub. - - -[[release-notes-5.11.0-RC1-junit-platform]] -=== JUnit Platform - -[[release-notes-5.11.0-RC1-junit-platform-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.11.0-RC1-junit-platform-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.11.0-RC1-junit-platform-new-features-and-improvements]] -==== New Features and Improvements - -* Introduce `@ConfigurationParametersResource` for `@Suite` classes and - `--config-resource` option for ConsoleLauncher that allow specifying additional - properties files on the classpath as sources of configuration parameters. -* New `rootCause()` condition in `TestExecutionResultConditions` that matches if an - exception's _root_ cause matches all supplied conditions, for use with the - `EngineTestKit`. -* `ReflectionSupport` now supports scanning for classpath resources. - - -[[release-notes-5.11.0-RC1-junit-jupiter]] -=== JUnit Jupiter - -[[release-notes-5.11.0-RC1-junit-jupiter-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.11.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.11.0-RC1-junit-jupiter-new-features-and-improvements]] -==== New Features and Improvements - -* New `argumentSet()` factory method for providing a custom name for an entire set of - arguments for a `@ParameterizedTest`. See the - <<../user-guide/index.adoc#writing-tests-parameterized-tests-display-names, User Guide>> - for details. -* `JAVA_24` has been added to the `JRE` enum for use with JRE-based execution conditions. -* New `assertInstanceOf` methods added for Kotlin following up with similar Java - `assertInstanceOf` methods introduced in `5.8` version. -* New generators in `DynamicTest` that take a `Stream`/`Iterator` of `Named` - along with a convenient `NamedExecutable` interface that can simplify writing dynamic - tests, in particular in recent version of Java that support records. - - -[[release-notes-5.11.0-RC1-junit-vintage]] -=== JUnit Vintage - -[[release-notes-5.11.0-RC1-junit-vintage-bug-fixes]] -==== Bug Fixes - -* ❓ - -[[release-notes-5.11.0-RC1-junit-vintage-deprecations-and-breaking-changes]] -==== Deprecations and Breaking Changes - -* ❓ - -[[release-notes-5.11.0-RC1-junit-vintage-new-features-and-improvements]] -==== New Features and Improvements - -* ❓ diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0.adoc new file mode 100644 index 000000000000..19fdbc72dfa0 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0.adoc @@ -0,0 +1,19 @@ +[[release-notes-5.11.0]] +== 5.11.0 + +*Date of Release:* August 14, 2024 + +*Scope:* + +* `@FieldSource` annotation for use with `@ParameterizedTest` methods +* Repeatable `@..Source` annotations for parameterized tests +* Enhancements for authoring dynamic and parameterized tests +* `@AutoClose` annotation to automatically close field resources in tests +* `ConversionSupport` utility for converting from a string to a supported target type +* Extensible syntax for specifying discovery selectors +* `@BeforeSuite` and `@AfterSuite` annotations +* Classpath resource scanning support for engines +* Numerous bug fixes and enhancements regarding field and method search algorithms + +For complete details consult the +https://junit.org/junit5/docs/5.11.0/release-notes/index.html[5.11.0 Release Notes] online. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.1.adoc new file mode 100644 index 000000000000..80af4a1a53d3 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.1.adoc @@ -0,0 +1,55 @@ +[[release-notes-5.11.1]] +== 5.11.1 + +*Date of Release:* September 25, 2024 + +*Scope:* Bug fixes and enhancements since 5.11.0 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/80?closed=1+[5.11.1] milestone page in the JUnit repository +on GitHub. + + +[[release-notes-5.11.1-junit-platform]] +=== JUnit Platform + +[[release-notes-5.11.1-junit-platform-bug-fixes]] +==== Bug Fixes + +* Fix support for disabling ANSI colors on the console when the `NO_COLOR` environment + variable is available. +* `NamespacedHierarchicalStore` no longer throws an exception after it has been closed if + the store is queried via one of the `get(...)` or `getOrComputeIfAbsent(...)` methods; + however, if a `getOrComputeIfAbsent(...)` invocation results in the computation of a new + value, an exception will still be thrown. +* Fixed potential locking issue with `ExclusiveResource` in the + `HierarchicalTestExecutorService`, which could lead to deadlocks in certain scenarios. + +[[release-notes-5.11.1-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* Improve parallelism and reduce number of blocked threads used by + `HierarchicalTestEngine` implementations when parallel execution is enabled and the + global read-write lock is used. + + +[[release-notes-5.11.1-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.11.1-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* `TestWatcher` callback methods can once again access data in the + `ExtensionContext.Store`. + +[[release-notes-5.11.1-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* Improve parallelism and reduce number of blocked threads in the presence of `@Isolated` + tests when parallel execution is enabled + + +[[release-notes-5.11.1-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc new file mode 100644 index 000000000000..0bb4b292e1d9 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.2.adoc @@ -0,0 +1,33 @@ +[[release-notes-5.11.2]] +== 5.11.2 + +*Date of Release:* October 4, 2024 + +*Scope:* Bug fixes and enhancements since 5.11.1 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/82?closed=1+[5.11.2] milestone page in the JUnit repository +on GitHub. + + +[[release-notes-5.11.2-junit-platform]] +=== JUnit Platform + +[[release-notes-5.11.2-junit-platform-bug-fixes]] +==== Bug Fixes + +* Fix regression in parallel execution that was introduced in 5.11.1 regarding global + read-write locks. When such a lock was declared on descendants of top-level nodes in the + test tree, such as Cucumber scenarios, test execution failed. + + +[[release-notes-5.11.2-junit-jupiter]] +=== JUnit Jupiter + +No changes. + + +[[release-notes-5.11.2-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc new file mode 100644 index 000000000000..0588af0f1cc7 --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.3.adoc @@ -0,0 +1,40 @@ +[[release-notes-5.11.3]] +== 5.11.3 + +*Date of Release:* October 21, 2024 + +*Scope:* Bug fixes and enhancements since 5.11.2 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/84?closed=1+[5.11.3] milestone page in the JUnit repository +on GitHub. + + +[[release-notes-5.11.3-junit-platform]] +=== JUnit Platform + +[[release-notes-5.11.3-junit-platform-bug-fixes]] +==== Bug Fixes + +* Fixed a regression in method search algorithms introduced in 5.11.0 when classes reside + in the default package and using a Java 8 runtime. + + +[[release-notes-5.11.3-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.11.3-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* Extensions can once again be registered via multiple `@ExtendWith` meta-annotations on + the same composed annotation on a field within a test class. +* `@ExtendWith` annotations can now also be repeated when used directly on fields and + parameters. +* All `@...Source` annotations of parameterized tests can now also be repeated when used + as meta annotations. + + +[[release-notes-5.11.3-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.4.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.4.adoc new file mode 100644 index 000000000000..e9ed32bc772a --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.11.4.adoc @@ -0,0 +1,39 @@ +[[release-notes-5.11.4]] +== 5.11.4 + +*Date of Release:* December 16, 2024 + +*Scope:* Bug fixes and enhancements since 5.11.3 + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/86?closed=1+[5.11.4] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-5.11.4-junit-platform]] +=== JUnit Platform + +[[release-notes-5.11.4-junit-platform-bug-fixes]] +==== Bug Fixes + +* Escape whitespace characters (such as line breaks) in XML attribute values (such as + exception messages) in the legacy XML report generated by the Console Launcher. This + change ensures the resulting XML files can be processed by downstream tools while + preserving whitespace characters. +* Enable auto-flushing of output in the `ConsoleLauncher` to fix issues with buffering, + in particular when using the `--details=testfeed` option. + + +[[release-notes-5.11.4-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.11.4-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* `JAVA_25` has been added to the `JRE` enum for use with JRE-based execution conditions. + + +[[release-notes-5.11.4-junit-vintage]] +=== JUnit Vintage + +No changes. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc new file mode 100644 index 000000000000..cbc023e40a0c --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-M1.adoc @@ -0,0 +1,178 @@ +[[release-notes-5.12.0-M1]] +== 5.12.0-M1 + +*Date of Release:* January 31, 2025 + +*Scope:* + +* Output file attachments for tests and containers +* Improvements to the Open Test Reporting XML output +* Resource lock definition improvements +* Thread dumps on test timeouts +* Parameterized test validation improvements +* Filtering support for auto-registered extensions +* Kotlin contracts for assertions +* Configurable Jupiter extension context scope +* Enhancements to the `ConsoleLauncher` +* Better support for GraalVM native image usage +* Improved discovery support for file-based test engines +* Customizable classpath scanning +* Parallel execution support in JUnit Vintage engine +* Numerous bug fixes and other enhancements + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/75?closed=1+[5.12.0-M1] milestone page in the +JUnit repository on GitHub. + + +[[release-notes-5.12.0-M1-overall-improvements]] +=== Overall Improvements + +[[release-notes-5.12.0-M1-overall-new-features-and-improvements]] +==== New Features and Improvements + +* All affected JAR files now include `native-image.properties` files that contain the + `--initialize-at-build-time` option to avoid breakages in GraalVM projects when updating + to newer versions of JUnit. + + +[[release-notes-5.12.0-M1-junit-platform]] +=== JUnit Platform + +[[release-notes-5.12.0-M1-junit-platform-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* `SearchOption` and `AnnotationSupport.findAnnotation(Class, Class, SearchOption)` from + `junit-platform-commons` have been deprecated. + +[[release-notes-5.12.0-M1-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* `ConsoleLauncher` now accepts multiple values for all `--select` options. +* `ConsoleLauncher` now supports a `--select-unique-id` option to select containers and + tests by unique ID. +* `ConsoleLauncher` supports new `--exclude-methodname` and `--include-methodname` options + to include or exclude methods based on fully qualified method names without parameters. + For example, `--exclude-methodname=^org\.example\..+#methodname` will exclude all + methods called `methodName` under package `org.example`. +* The `--select-file` and `--select-resource` options for the `ConsoleLauncher` now + support line and column numbers. +* New `ReflectionSupport.makeAccessible(Field)` public utility method to be used by third + parties instead of calling the internal `ReflectionUtils.makeAccessible(Field)` method + directly. +* The `ReflectionSupport.tryToLoadClass(...)` utility methods now support lookups for the + `"void"` pseudo-type, which indirectly supports `String` to `Class` conversion for + `"void"` in parameterized tests in JUnit Jupiter. +* New `addResourceContainerSelectorResolver()` method in + `EngineDiscoveryRequestResolver.Builder` which supports the discovery of class path + resource based tests, analogous to the existing `addClassContainerSelectorResolver()` + method. +* New `getOutputDirectoryProvider()` method in `EngineDiscoveryRequest` and `TestPlan` to + allow test engines to publish/attach files to containers and tests by calling + `EngineExecutionListener.fileEntryPublished(...)`. Registered `TestExecutionListeners` + can then access these files by overriding the `fileEntryPublished(...)` method. +* The following improvements have been made to the + <<../user-guide/index.adoc#junit-platform-reporting-open-test-reporting, Open Test Reporting>> + XML output: + - Information about the Git repository, the current branch, the commit hash, and the + current worktree status are now included in the XML report, if applicable. + - A section containing JUnit-specific metadata about each test/container to the HTML + report is now written by open-test-reporting when added to the classpath/module path + - Information about published files is now included as attachments. + - If <<../user-guide/index.adoc#running-tests-capturing-output, output capturing>> is + enabled, the captured output written to `System.out` and `System.err` is now included + in the XML report. +* Output written to `System.out` and `System.err` from non-test threads is now attributed + to the most recent test or container that was started or has written output. +* New public interface `ClasspathScanner` allowing third parties to provide a custom + implementation for scanning the classpath for classes and resources. +* New `AnnotationSupport.findAnnotation(Class, Class, List)` method to support searching + for an annotation on an inner class and its runtime enclosing instance types. + + +[[release-notes-5.12.0-M1-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.12.0-M1-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* Provide _runtime_ enclosing types of `@Nested` test classes and contained test methods + to `DisplayNameGenerator` implementations. Prior to this change, such generators were + only able to access the enclosing class in which `@Nested` was declared, but they could + not access the concrete runtime type of the enclosing instance. +* `@DisplayNameGeneration` annotations are now discovered on the _runtime_ enclosing types + of `@Nested` test classes instead of the compile-time enclosing class in which the + `@Nested` class was _declared_. + +[[release-notes-5.12.0-M1-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* When injecting `TestInfo` into test class constructors, the `TestInfo` now contains data + for the test method for which the test class instance is being created, unless the test + instance lifecycle is set to `PER_CLASS` (in which case it continues to contain the data + for the test class). If you require the `TestInfo` of the test class, you can implement + a `@BeforeAll` lifecycle method and inject `TestInfo` into that method. +* When injecting `TestReporter` into test class constructors the published report entries + are now associated with the test method rather than the test class, unless the test + instance lifecycle is set to `PER_CLASS` (in which case the published report entries + will continue to be associated with the test class). If you want to publish report + entries for the test class, you can implement a `@BeforeAll` lifecycle method and inject + `TestReporter` into that method. + +[[release-notes-5.12.0-M1-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* Kotlin contracts for Kotlin-specific assertion methods in `Assertions`. +* `@TempDir` is now supported on test class constructors. +* Shared resource locks may now be determined programmatically at runtime via the new + `@ResourceLock#providers` attribute that accepts implementations of + `ResourceLocksProvider`. +* Shared resource locks for _direct_ child nodes may now be configured via the new + `@ResourceLock(target = CHILDREN)` attribute. This may improve parallelization when + a test class declares a `READ` lock, but only a few methods hold a `READ_WRITE` lock. +* `@EnumSource` has new `from` and `to` attributes that support the selection of enum + constants within the specified range. +* In a `@ParameterizedTest` method, a `null` value can now be supplied for Java Date/Time + types such as `LocalDate` if the new `nullable` attribute in + `@JavaTimeConversionPattern` is set to `true`. +* The new `@ParameterizedTest(allowZeroInvocations = true)` attribute allows to specify that + the absence of invocations is expected in some cases and should not cause a test failure. +* Parameterized tests now support argument count validation. If the + `junit.jupiter.params.argumentCountValidation=strict` configuration parameter or the + `@ParameterizedTest(argumentCountValidation = STRICT)` attribute is set, any mismatch + between the declared number of arguments and the number of arguments provided by the + arguments source will result in an error. By default, it is still only an error if there + are fewer arguments provided than declared. +* `ArgumentsProvider` (declared via `@ArgumentsSource`), `ArgumentConverter` (declared via + `@ConvertWith`), and `ArgumentsAggregator` (declared via `@AggregateWith`) + implementations can now use constructor injection from registered `ParameterResolver` + extensions. +* `TestTemplateInvocationContextProvider` extensions can now signal that they may + potentially return zero invocation contexts by overriding the new + `mayReturnZeroTestTemplateInvocationContexts()` method. +* Extensions that implement `TestInstancePreConstructCallback`, `TestInstanceFactory`, + `TestInstancePostProcessor`, `ParameterResolver`, or `InvocationInterceptor` may + override the `getTestInstantiationExtensionContextScope()` method to enable receiving + a test-scoped `ExtensionContext` in `Extension` methods called during test class + instantiation. This behavior will become the default in future versions of JUnit. +* The new `PreInterruptCallback` interface defines the API for `Extensions` that wish to + be called prior to invocations of `Thread#interrupt()` by the `@Timeout` extension. +* When enabled via the `junit.jupiter.execution.timeout.threaddump.enabled` configuration + parameter, an implementation of `PreInterruptCallback` is registered that writes a + thread dump to `System.out` prior to interrupting a test thread due to a timeout. +* `TestReporter` now allows publishing files for a test method or test class which can be + used to include them in test reports, such as the Open Test Reporting format. +* Auto-registered extensions can now be + <<../user-guide/index.adoc#extensions-registration-automatic-filtering, filtered>> using + include and exclude patterns that can be specified as configuration parameters. + + +[[release-notes-5.12.0-M1-junit-vintage]] +=== JUnit Vintage + +[[release-notes-5.12.0-M1-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* Support for executing top-level test classes in parallel. Please refer to the + <<../user-guide/index.adoc#migrating-from-junit4-parallel-execution, User Guide>> for + more information. diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-RC1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-RC1.adoc new file mode 100644 index 000000000000..7c890f6b1cce --- /dev/null +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.12.0-RC1.adoc @@ -0,0 +1,70 @@ +[[release-notes-5.12.0-RC1]] +== 5.12.0-RC1 + +*Date of Release:* ❓ + +*Scope:* Minor enhancements since JUnit 5.12 M1. + +For a complete list of all _closed_ issues and pull requests for this release, consult the +link:{junit5-repo}+/milestone/88?closed=1+[5.12.0-RC1] milestone page in the JUnit +repository on GitHub. + + +[[release-notes-5.12.0-RC1-junit-platform]] +=== JUnit Platform + +[[release-notes-5.12.0-RC1-junit-platform-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.12.0-RC1-junit-platform-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.12.0-RC1-junit-platform-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ + + +[[release-notes-5.12.0-RC1-junit-jupiter]] +=== JUnit Jupiter + +[[release-notes-5.12.0-RC1-junit-jupiter-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.12.0-RC1-junit-jupiter-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.12.0-RC1-junit-jupiter-new-features-and-improvements]] +==== New Features and Improvements + +* `JRE`-based conditions such as `@EnabledOnJre` and `@DisabledForJreRange` now support + arbitrary Java versions. See the + <<../user-guide/index.adoc#writing-tests-conditional-execution-jre, User Guide>> for + details. + + +[[release-notes-5.12.0-RC1-junit-vintage]] +=== JUnit Vintage + +[[release-notes-5.12.0-RC1-junit-vintage-bug-fixes]] +==== Bug Fixes + +* ❓ + +[[release-notes-5.12.0-RC1-junit-vintage-deprecations-and-breaking-changes]] +==== Deprecations and Breaking Changes + +* ❓ + +[[release-notes-5.12.0-RC1-junit-vintage-new-features-and-improvements]] +==== New Features and Improvements + +* ❓ diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc index 287917904eea..2e5b4d7369f1 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-reporting.adoc @@ -3,41 +3,49 @@ The `junit-platform-reporting` artifact contains `{TestExecutionListener}` implementations that generate XML test reports in two flavors: -<> and -<>. +<> and +<>. NOTE: The module also contains other `TestExecutionListener` implementations that can be used to build custom reporting. See <> for details. -[[junit-platform-reporting-legacy-xml]] -==== Legacy XML format +[[junit-platform-reporting-output-directory]] +==== Output Directory -`{LegacyXmlReportGeneratingListener}` generates a separate XML report for each root in the -`{TestPlan}`. Note that the generated XML format is compatible with the de facto standard -for JUnit 4 based test reports that was made popular by the Ant build system. +The JUnit Platform provides an `{OutputDirectoryProvider}` via +`{EngineDiscoveryRequest}` and `{TestPlan}` to registered <> +and <>, respectively. Its root directory can be +configured via the following <>: -The `LegacyXmlReportGeneratingListener` is used by the <> -as well. +`junit.platform.reporting.output.dir=`:: + Configure the output directory for reporting. By default, `build` is used if a Gradle + build script is found, and `target` if a Maven POM is found; otherwise, the current + working directory is used. + +To create a unique output directory per test run, you can use the `\{uniqueNumber}` +placeholder in the path. For example, `reports/junit-\{uniqueNumber}` will create +directories like `reports/junit-8803697269315188212`. This can be useful when using +Gradle's or Maven's parallel execution capabilities which create multiple JVM forks +that run concurrently. [[junit-platform-reporting-open-test-reporting]] -==== Open Test Reporting XML format +==== Open Test Reporting `{OpenTestReportGeneratingListener}` writes an XML report for the entire execution in the event-based format specified by {OpenTestReporting} which supports all features of the JUnit Platform such as hierarchical test structures, display names, tags, etc. The listener is auto-registered and can be configured via the following -<>: +<>: `junit.platform.reporting.open.xml.enabled=true|false`:: Enable/disable writing the report. -`junit.platform.reporting.output.dir=`:: - Configure the output directory for the reports. By default, `build` is used if a Gradle - build script is found, and `target` if a Maven POM is found; otherwise, the current - working directory is used. -If enabled, the listener creates an XML report file named -`junit-platform-events-.xml` per test run in the configured output directory. +If enabled, the listener creates an XML report file named `open-test-report.xml` in the +configured <>. + +If <> is enabled, the captured output +written to `System.out` and `System.err` will be included in the report as well. TIP: The {OpenTestReportingCliTool} can be used to convert from the event-based format to the hierarchical format which is more human-readable. @@ -145,3 +153,13 @@ via the `--config-resource` option: $ java -jar junit-platform-console-standalone-{platform-version}.jar \ --config-resource=configuration.properties ---- + +[[junit-platform-reporting-legacy-xml]] +==== Legacy XML format + +`{LegacyXmlReportGeneratingListener}` generates a separate XML report for each root in the +`{TestPlan}`. Note that the generated XML format is compatible with the de facto standard +for JUnit 4 based test reports that was made popular by the Ant build system. + +The `LegacyXmlReportGeneratingListener` is used by the <> +as well. diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc index d38a312d799f..ebb1d6495b04 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/junit-platform-suite-engine.adoc @@ -48,3 +48,14 @@ include::{testDir}/example/SuiteDemo.java[tags=user_guide] NOTE: There are numerous configuration options for discovering and filtering tests in a test suite. Please consult the Javadoc of the `{suite-api-package}` package for a full list of supported annotations and further details. + +==== @BeforeSuite and @AfterSuite + +`@BeforeSuite` and `@AfterSuite` annotations can be used on methods inside a +`@Suite`-annotated class. They will be executed respectively before and after +all tests of the test suite. + +[source,java,indent=0] +---- +include::{testDir}/example/BeforeAndAfterSuiteDemo.java[tags=user_guide] +---- diff --git a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc index c4978a431362..e1fb5e37efca 100644 --- a/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc +++ b/documentation/src/docs/asciidoc/user-guide/advanced-topics/launcher-api.adoc @@ -173,6 +173,16 @@ former, clients can register custom implementations of `{LauncherInterceptor}` v `junit.platform.launcher.interceptors.enabled` <> to `true`. +[NOTE] +==== +Since interceptors are registered before the test run starts, the +`junit.platform.launcher.interceptors.enabled` _configuration parameter_ can only be +supplied as a JVM system property or via the JUnit Platform configuration file (see +<> for details). This _configuration parameter_ cannot be +supplied in the `LauncherDiscoveryRequest` that is passed to the `{Launcher}`. +==== + + A typical use case is to create a custom interceptor to replace the `ClassLoader` used by the JUnit Platform to load test classes and engine implementations. diff --git a/documentation/src/docs/asciidoc/user-guide/appendix.adoc b/documentation/src/docs/asciidoc/user-guide/appendix.adoc index 2f3ae2bf5e93..ab63a5a90b3b 100644 --- a/documentation/src/docs/asciidoc/user-guide/appendix.adoc +++ b/documentation/src/docs/asciidoc/user-guide/appendix.adoc @@ -21,6 +21,26 @@ Artifacts for final releases and milestones are deployed to {Maven_Central}, and artifacts are deployed to Sonatype's {snapshot-repo}[snapshots repository] under {snapshot-repo}/org/junit/[/org/junit]. +The sections below list all artifacts with their versions for the three groups: +<>, +<>, and +<>. +The <> contains a list of all +of the above artifacts and their versions. + +[TIP] +.Aligning dependency versions +==== +To ensure that all JUnit artifacts are compatible with each other, their versions should +be aligned. +If you rely on <> for dependency management, +please see the corresponding section. +Otherwise, instead of managing individual versions of the JUnit artifacts, it is +recommended to apply the <> to your project. +Please refer to the corresponding sections for <> or +<>. +==== + [[dependency-metadata-junit-platform]] ==== JUnit Platform diff --git a/documentation/src/docs/asciidoc/user-guide/extensions.adoc b/documentation/src/docs/asciidoc/user-guide/extensions.adoc index b5e4e89dc614..11185fe05019 100644 --- a/documentation/src/docs/asciidoc/user-guide/extensions.adoc +++ b/documentation/src/docs/asciidoc/user-guide/extensions.adoc @@ -129,21 +129,9 @@ important to note which extension APIs are implemented and for what reasons. Specifically, `RandomNumberExtension` implements the following extension APIs: - `BeforeAllCallback`: to support static field injection -- `BeforeEachCallback`: to support non-static field injection +- `TestInstancePostProcessor`: to support non-static field injection - `ParameterResolver`: to support constructor and method injection -[NOTE] -==== -Ideally, the `RandomNumberExtension` would implement `TestInstancePostProcessor` instead -of `BeforeEachCallback` in order to support non-static field injection immediately after -the test class has been instantiated. - -However, JUnit Jupiter currently does not allow a `TestInstancePostProcessor` to be -registered via `@ExtendWith` on a non-static field (see -link:{junit5-repo}/issues/3437[issue 3437]). In light of that, the `RandomNumberExtension` -implements `BeforeEachCallback` as an alternative approach. -==== - [source,java,indent=0] ---- include::{testDir}/example/extensions/RandomNumberExtension.java[tags=user_guide] @@ -272,11 +260,8 @@ will be registered after the test class has been instantiated and after each reg (potentially injecting the instance of the extension to be used into the annotated field). Thus, if such an _instance extension_ implements class-level or instance-level extension APIs such as `BeforeAllCallback`, `AfterAllCallback`, or -`TestInstancePostProcessor`, those APIs will not be honored. By default, an instance -extension will be registered _after_ extensions that are registered at the method level -via `@ExtendWith`; however, if the test class is configured with -`@TestInstance(Lifecycle.PER_CLASS)` semantics, an instance extension will be registered -_before_ extensions that are registered at the method level via `@ExtendWith`. +`TestInstancePostProcessor`, those APIs will not be honored. Instance extensions will be +registered _before_ extensions that are registered at the method level via `@ExtendWith`. In the following example, the `docs` field in the test class is initialized programmatically by invoking a custom `lookUpDocsDir()` method and supplying the result @@ -322,6 +307,23 @@ When auto-detection is enabled, extensions discovered via the `{ServiceLoader}` will be added to the extension registry after JUnit Jupiter's global extensions (e.g., support for `TestInfo`, `TestReporter`, etc.). +[[extensions-registration-automatic-filtering]] +===== Filtering Auto-detected Extensions + +The list of auto-detected extensions can be filtered using include and exclude patterns +via the following <>: + +`junit.jupiter.extensions.autodetection.include=`:: + Comma-separated list of _include_ patterns for auto-detected extensions. +`junit.jupiter.extensions.autodetection.exclude=`:: + Comma-separated list of _exclude_ patterns for auto-detected extensions. + +Include patterns are applied _before_ exclude patterns. If both include and exclude +patterns are provided, only extensions that match at least one include pattern and do not +match any exclude pattern will be auto-detected. + +See <> for details on the pattern syntax. + [[extensions-registration-inheritance]] ==== Extension Inheritance @@ -396,6 +398,14 @@ This extension provides a symmetric call to `{TestInstancePreDestroyCallback}` a in combination with other extensions to prepare constructor parameters or keeping track of test instances and their lifecycle. +[NOTE] +.Accessing the test-scoped `ExtensionContext` +==== +You may override the `getTestInstantiationExtensionContextScope(...)` method to return +`TEST_METHOD` to make test-specific data available to your extension implementation or if +you want to <> on the test method level. +==== + [[extensions-test-instance-factories]] === Test Instance Factories @@ -422,6 +432,14 @@ the user's responsibility to ensure that only a single `TestInstanceFactory` is registered for any specific test class. ==== +[NOTE] +.Accessing the test-scoped `ExtensionContext` +==== +You may override the `getTestInstantiationExtensionContextScope(...)` method to return +`TEST_METHOD` to make test-specific data available to your extension implementation or if +you want to <> on the test method level. +==== + [[extensions-test-instance-post-processing]] === Test Instance Post-processing @@ -434,6 +452,14 @@ initialization methods on the test instance, etc. For a concrete example, consult the source code for the `{MockitoExtension}` and the `{SpringExtension}`. +[NOTE] +.Accessing the test-scoped `ExtensionContext` +==== +You may override the `getTestInstantiationExtensionContextScope(...)` method to return +`TEST_METHOD` to make test-specific data available to your extension implementation or if +you want to <> on the test method level. +==== + [[extensions-test-instance-pre-destroy-callback]] === Test Instance Pre-destroy Callback @@ -481,6 +507,17 @@ those provided in `java.lang.reflect.Parameter` in order to avoid this bug in th ==== [NOTE] +.Accessing the test-scoped `ExtensionContext` +==== +You may override the `getTestInstantiationExtensionContextScope(...)` method to return +`TEST_METHOD` to support injecting test specific data into constructor parameters of the +test class instance. Doing so causes a test-specific `{ExtensionContext}` to be used while +resolving constructor parameters, unless the +<> is set to `PER_CLASS`. +==== + +[TIP] +.Parameter resolution for methods called from extensions ==== Other extensions can also leverage registered `ParameterResolvers` for method and constructor invocations, using the `{ExecutableInvoker}` available via the @@ -695,6 +732,15 @@ test methods. include::{testDir}/example/exception/MultipleHandlersTestCase.java[tags=user_guide] ---- +[[extensions-preinterrupt-callback]] +=== Pre-Interrupt Callback + +`{PreInterruptCallback}` defines the API for `Extensions` that wish to react on +timeouts before the `Thread.interrupt()` is called. + +Please refer to <> for additional information. + + [[extensions-intercepting-invocations]] === Intercepting Invocations @@ -710,6 +756,15 @@ Dispatch Thread. include::{testDir}/example/interceptor/SwingEdtInterceptor.java[tags=user_guide] ---- +[NOTE] +.Accessing the test-scoped `ExtensionContext` +==== +You may override the `getTestInstantiationExtensionContextScope(...)` method to return +`TEST_METHOD` to make test-specific data available to your extension implementation of +`interceptTestClassConstructor` or if you want to <> +on the test method level. +==== + [[extensions-test-templates]] === Providing Invocation Contexts for Test Templates @@ -816,6 +871,9 @@ and fields in a class or interface. Some of these methods search on implemented interfaces and within class hierarchies to find annotations. Consult the Javadoc for `{AnnotationSupport}` for further details. +NOTE: The `isAnnotated()` methods do not find repeatable annotations. To check for repeatable annotations, +use one of the `findRepeatableAnnotations()` methods and verify that the returned list is not empty. + NOTE: See also: <> [[extensions-supported-utilities-classes]] diff --git a/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc b/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc index 98e0b056af93..725b113dedc1 100644 --- a/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc +++ b/documentation/src/docs/asciidoc/user-guide/migration-from-junit4.adoc @@ -36,6 +36,31 @@ annotated with `@Category(Example.class)`, it will be tagged with `"com.acme.Exa Similar to the `Categories` runner in JUnit 4, this information can be used to filter the discovered tests before executing them (see <> for details). +[[migrating-from-junit4-parallel-execution]] +=== Parallel Execution + +The JUnit Vintage test engine supports parallel execution of top-level test classes, +allowing existing JUnit 3 and JUnit 4 tests to benefit from improved performance through +concurrent test execution. It can be enabled and configured using the following +<>: + +`junit.vintage.execution.parallel.enabled=true|false`:: + Enable/disable parallel execution (defaults to `false`). + +`junit.vintage.execution.parallel.pool-size=`:: + Specifies the size of the thread pool to be used for parallel execution. By default, the + number of available processors is used. + +Example configuration in `junit-platform.properties`: + +[source,properties] +---- +junit.vintage.execution.parallel.enabled=true +junit.vintage.execution.parallel.pool-size=4 +---- + +With these properties set, the `VintageTestEngine` will execute tests in parallel, +potentially significantly reducing the overall test suite execution time. [[migrating-from-junit4-tips]] === Migration Tips diff --git a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc index 734071e821cb..73c1ffc13f47 100644 --- a/documentation/src/docs/asciidoc/user-guide/running-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/running-tests.adoc @@ -975,6 +975,7 @@ parameters_ used for the following features. - <> - <> - <> +- <> If the value for the given _configuration parameter_ consists solely of an asterisk (`+++*+++`), the pattern will match against all candidate classes. Otherwise, the value diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 3ef36ab200bd..8442461ae2e1 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -1,3 +1,7 @@ +:testDir: ../../../../src/test/java +:testRelease21Dir: ../../../../src/test/java21 +:kotlinTestDir: ../../../../src/test/kotlin + [[writing-tests]] == Writing Tests @@ -43,7 +47,7 @@ in the `junit-jupiter-api` module. | `@Disabled` | Used to <> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not inherited. | `@AutoClose` | Denotes that the annotated field represents a resource that will be <> after test execution. | `@Timeout` | Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are inherited. -| `@TempDir` | Used to supply a <> via field injection or parameter injection in a lifecycle method or test method; located in the `org.junit.jupiter.api.io` package. Such fields are inherited. +| `@TempDir` | Used to supply a <> via field injection or parameter injection in a test class constructor, lifecycle method, or test method; located in the `org.junit.jupiter.api.io` package. Such fields are inherited. | `@ExtendWith` | Used to <>. Such annotations are inherited. | `@RegisterExtension` | Used to <> via fields. Such fields are inherited. |=== @@ -120,6 +124,7 @@ Test Class:: any top-level class, `static` member class, or <> that contains at least one _test method_, i.e. a _container_. Test classes must not be `abstract` and must have a single constructor. +Java `record` classes are supported as well. Test Method:: any instance method that is directly annotated or meta-annotated with @@ -178,6 +183,15 @@ lifecycle methods. For further information on runtime semantics, see include::{testDir}/example/StandardTests.java[tags=user_guide] ---- +It is also possible to use Java `record` classes as test classes as illustrated by the +following example. + +[source,java,indent=0] +.A test class written as a Java record +---- +include::{testRelease21Dir}/example/MyFirstJUnitJupiterRecordTests.java[tags=user_guide] +---- + [[writing-tests-display-names]] === Display Names @@ -279,6 +293,13 @@ JUnit Jupiter comes with many of the assertion methods that JUnit 4 has and adds that lend themselves well to being used with Java 8 lambdas. All JUnit Jupiter assertions are `static` methods in the `{Assertions}` class. +Assertion methods optionally accept the assertion message as their third parameter, which +can be either a `String` or a `Supplier`. + +When using a `Supplier` (e.g., a lambda expression), the message is evaluated +lazily. This can provide a performance benefit, especially if message construction is +complex or time-consuming, as it is only evaluated when the assertion fails. + [source,java,indent=0] ---- include::{testDir}/example/AssertionsDemo.java[tags=user_guide] @@ -625,18 +646,36 @@ include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_arc [[writing-tests-conditional-execution-jre]] ==== Java Runtime Environment Conditions -A container or test may be enabled or disabled on particular versions of the Java -Runtime Environment (JRE) via the `{EnabledOnJre}` and `{DisabledOnJre}` annotations -or on a particular range of versions of the JRE via the `{EnabledForJreRange}` and -`{DisabledForJreRange}` annotations. The range defaults to `{JRE}.JAVA_8` as the lower -border (`min`) and `{JRE}.OTHER` as the higher border (`max`), which allows usage of -half open ranges. +A container or test may be enabled or disabled on particular versions of the Java Runtime +Environment (JRE) via the `{EnabledOnJre}` and `{DisabledOnJre}` annotations or on a +particular range of versions of the JRE via the `{EnabledForJreRange}` and +`{DisabledForJreRange}` annotations. The range effectively defaults to `JRE.JAVA_8` as the +lower bound and `JRE.OTHER` as the upper bound, which allows usage of half open ranges. + +The following listing demonstrates the use of these annotations with predefined {JRE} enum +constants. [source,java,indent=0] ---- include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre] ---- +Since the enum constants defined in {JRE} are static for any given JUnit release, you +might find that you need to configure a Java version that is not supported by the `JRE` +enum. For example, as of JUnit Jupiter 5.12 the `JRE` enum defines `JAVA_25` as the +highest supported Java version. However, you may wish to run your tests against later +versions of Java. To support such use cases, you can specify arbitrary Java versions via +the `versions` attributes in `@EnabledOnJre` and `@DisabledOnJre` and via the `minVersion` +and `maxVersion` attributes in `@EnabledForJreRange` and `@DisabledForJreRange`. + +The following listing demonstrates the use of these annotations with arbitrary Java +versions. + +[source,java,indent=0] +---- +include::{testDir}/example/ConditionalTestExecutionDemo.java[tags=user_guide_jre_arbitrary_versions] +---- + [[writing-tests-conditional-execution-native]] ==== Native Image Conditions @@ -1037,8 +1076,8 @@ There are currently three built-in resolvers that are registered automatically. method, or a custom name configured via `@DisplayName`. + `{TestInfo}` acts as a drop-in replacement for the `TestName` rule from JUnit 4. The -following demonstrates how to have `TestInfo` injected into a test constructor, -`@BeforeEach` method, and `@Test` method. +following demonstrates how to have `TestInfo` injected into a `@BeforeAll` method, test +class constructor, `@BeforeEach` method, and `@Test` method. [source,java,indent=0] ---- @@ -1056,8 +1095,9 @@ include::{testDir}/example/TestInfoDemo.java[tags=user_guide] * `{TestReporterParameterResolver}`: if a constructor or method parameter is of type `{TestReporter}`, the `TestReporterParameterResolver` will supply an instance of `TestReporter`. The `TestReporter` can be used to publish additional data about the - current test run. The data can be consumed via the `reportingEntryPublished()` method in - a `{TestExecutionListener}`, allowing it to be viewed in IDEs or included in reports. + current test run or attach files to it. The data can be consumed in a + `{TestExecutionListener}` via the `reportingEntryPublished()` or `fileEntryPublished()` + method, respectively. This allows them to be viewed in IDEs or included in reports. + In JUnit Jupiter you should use `TestReporter` where you used to print information to `stdout` or `stderr` in JUnit 4. Using `@RunWith(JUnitPlatform.class)` will output all @@ -1538,14 +1578,28 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_example_au ---- The annotation provides an optional `names` attribute that lets you specify which -constants shall be used, like in the following example. If omitted, all constants will be -used. +constants shall be used, like in the following example. [source,java,indent=0] ---- include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_include_example] ---- +In addition to `names`, you can use the `from` and `to` attributes to specify a range of +constants. The range starts from the constant specified in the `from` attribute and +includes all subsequent constants up to and including the one specified in the `to` +attribute, based on the natural order of the enum constants. + +If `from` and `to` attributes are omitted, they default to the first and last constants +in the enum type, respectively. If all `names`, `from`, and `to` attributes are omitted, +all constants will be used. The following example demonstrates how to specify a range of +constants. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_range_example] +---- + The `@EnumSource` annotation also provides an optional `mode` attribute that enables fine-grained control over which constants are passed to the test method. For example, you can exclude names from the enum constant pool or specify regular expressions as in the @@ -1561,6 +1615,14 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_exclude_ex include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_regex_example] ---- +You can also combine `mode` with the `from`, `to` and `names` attributes to define a +range of constants while excluding specific values from that range as shown below. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=EnumSource_range_exclude_example] +---- + [[writing-tests-parameterized-tests-sources-MethodSource]] ===== @MethodSource @@ -1963,6 +2025,15 @@ If you wish to implement a custom `ArgumentsProvider` that also consumes an anno (like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`), you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class. +Moreover, `ArgumentsProvider` implementations may declare constructor parameters in case +they need to be resolved by a registered `ParameterResolver` as demonstrated in the +following example. + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=ArgumentsProviderWithConstructorInjection_example] +---- + [[writing-tests-parameterized-repeatable-sources]] ===== Multiple sources using repeatable annotations Repeatable annotations provide a convenient way to specify multiple sources from @@ -1990,6 +2061,29 @@ The following annotations are repeatable: * `@CsvFileSource` * `@ArgumentsSource` +[[writing-tests-parameterized-tests-argument-count-validation]] +==== Argument Count Validation + +WARNING: Argument count validation is currently an _experimental_ feature. You're invited to +give it a try and provide feedback to the JUnit team so they can improve and eventually +<> this feature. + +By default, when an arguments source provides more arguments than the test method needs, +those additional arguments are ignored and the test executes as usual. +This can lead to bugs where arguments are never passed to the parameterized test method. + +To prevent this, you can set argument count validation to 'strict'. +Then, any additional arguments will cause an error instead. + +To change this behavior for all tests, set the `junit.jupiter.params.argumentCountValidation` +<> to `strict`. +To change this behavior for a single test, +use the `argumentCountValidation` attribute of the `@ParameterizedTest` annotation: + +[source,java,indent=0] +---- +include::{testDir}/example/ParameterizedTestDemo.java[tags=argument_count_validation] +---- [[writing-tests-parameterized-tests-argument-conversion]] ==== Argument Conversion @@ -2414,10 +2508,6 @@ lambda expression for a dynamic test, those fields will not be reset by callback or extensions between the execution of individual dynamic tests generated by the same `@TestFactory` method. -As of JUnit Jupiter {jupiter-version}, dynamic tests must always be created by factory -methods; however, this might be complemented by a registration facility in a later -release. - [[writing-tests-dynamic-tests-examples]] ==== Dynamic Test Examples @@ -2453,6 +2543,20 @@ a nested hierarchy of dynamic tests utilizing `DynamicContainer`. include::{testDir}/example/DynamicTestsDemo.java[tags=user_guide] ---- +[[writing-tests-dynamic-tests-named-support]] +==== Dynamic Tests and Named + +In some cases, it can be more natural to specify inputs together with a descriptive name +using the {Named} API and the corresponding `stream()` factory methods on `DynamicTest` as +shown in the first example below. The second example takes it one step further and allows +to provide the code block that should be executed by implementing the `Executable` +interface along with `Named` via the `NamedExecutable` base class. + +[source,java] +---- +include::{testRelease21Dir}/example/DynamicTestsNamedDemo.java[tags=user_guide] +---- + [[writing-tests-dynamic-tests-uri-test-source]] ==== URI Test Sources for Dynamic Tests @@ -2619,8 +2723,27 @@ asynchronous tests, consider using a dedicated library such as link:https://github.com/awaitility/awaitility[Awaitility]. +[[writing-tests-declarative-timeouts-debugging]] +==== Debugging Timeouts + +Registered <> extensions are called prior to invoking +`Thread.interrupt()` on the thread that is executing the timed out method. This allows to +inspect the application state and output additional information that might be helpful for +diagnosing the cause of a timeout. + + +[[writing-tests-declarative-timeouts-debugging-thread-dump]] +===== Thread Dump on Timeout + +JUnit registers a default implementation of the <> +extension point that dumps the stacks of all threads to `System.out` if enabled by setting +the `junit.jupiter.execution.timeout.threaddump.enabled` +<> to `true`. + + [[writing-tests-declarative-timeouts-mode]] ==== Disable @Timeout Globally + When stepping through your code in a debug session, a fixed timeout limit may influence the result of the test, e.g. mark the test as failed although all assertions were met. @@ -2670,11 +2793,11 @@ junit.jupiter.execution.parallel.mode.default = concurrent The default execution mode is applied to all nodes of the test tree with a few notable exceptions, namely test classes that use the `Lifecycle.PER_CLASS` mode or a -`{MethodOrderer}` (except for `{MethodOrderer_Random}`). In the former case, test authors -have to ensure that the test class is thread-safe; in the latter, concurrent execution -might conflict with the configured execution order. Thus, in both cases, test methods in -such test classes are only executed concurrently if the `@Execution(CONCURRENT)` -annotation is present on the test class or method. +`{MethodOrderer}`. In the former case, test authors have to ensure that the test class is +thread-safe; in the latter, concurrent execution might conflict with the configured +execution order. Thus, in both cases, test methods in such test classes are only executed +concurrently if the `@Execution(CONCURRENT)` annotation is present on the test class or +method. When parallel execution is enabled and a default `{ClassOrderer}` is registered (see <> for details), top-level test classes will @@ -2903,8 +3026,14 @@ execution. The shared resource is identified by a unique name which is a `String name can be user-defined or one of the predefined constants in `{Resources}`: `SYSTEM_PROPERTIES`, `SYSTEM_OUT`, `SYSTEM_ERR`, `LOCALE`, or `TIME_ZONE`. +In addition to declaring these shared resources statically, the `{ResourceLock}` +annotation has a `providers` attribute that allows registering implementations of the +`{ResourceLocksProvider}` interface that can add shared resources dynamically at runtime. +Note that resources declared statically with `{ResourceLock}` annotation are combined with +resources added dynamically by `{ResourceLocksProvider}` implementations. + If the tests in the following example were run in parallel _without_ the use of -{ResourceLock}, they would be _flaky_. Sometimes they would pass, and at other times they +`{ResourceLock}`, they would be _flaky_. Sometimes they would pass, and at other times they would fail due to the inherent race condition of writing and then reading the same JVM System Property. @@ -2930,8 +3059,37 @@ parallel with each other but not while any other test that requires `READ_WRITE` to the same shared resource is running. [source,java] +.Declaring shared resources "statically" with `{ResourceLock}` annotation +---- +include::{testDir}/example/sharedresources/StaticSharedResourcesDemo.java[tags=user_guide] +---- + +[source,java] +.Adding shared resources "dynamically" with `{ResourceLocksProvider}` implementation +---- +include::{testDir}/example/sharedresources/DynamicSharedResourcesDemo.java[tags=user_guide] +---- + +Also, "static" shared resources can be declared for _direct_ child nodes via the `target` +attribute in the `{ResourceLock}` annotation, the attribute accepts a value from +the `{ResourceLockTarget}` enum. + +Specifying `target = CHILDREN` in a class-level `{ResourceLock}` annotation +has the same semantics as adding an annotation with the same `value` and `mode` +to each test method and nested test class declared in this class. + +This may improve parallelization when a test class declares a `READ` lock, +but only a few methods hold a `READ_WRITE` lock. + +Tests in the following example would run in the `SAME_THREAD` if the `{ResourceLock}` +didn't have `target = CHILDREN`. This is because the test class declares a `READ` +shared resource, but one test method holds a `READ_WRITE` lock, +which would force the `SAME_THREAD` execution mode for all the test methods. + +[source,java] +.Declaring shared resources for child nodes with `target` attribute ---- -include::{testDir}/example/SharedResourcesDemo.java[tags=user_guide] +include::{testDir}/example/sharedresources/ChildrenSharedResourcesDemo.java[tags=user_guide] ---- @@ -2950,7 +3108,8 @@ The built-in `{TempDirectory}` extension is used to create and clean up a tempor directory for an individual test or all tests in a test class. It is registered by default. To use it, annotate a non-final, unassigned field of type `java.nio.file.Path` or `java.io.File` with `{TempDir}` or add a parameter of type `java.nio.file.Path` or -`java.io.File` annotated with `@TempDir` to a lifecycle method or test method. +`java.io.File` annotated with `@TempDir` to a test class constructor, lifecycle method, or +test method. For example, the following test declares a parameter annotated with `@TempDir` for a single test method, creates and writes to a file in the temporary directory, and checks @@ -2975,14 +3134,10 @@ entire test class or method (depending on which level the annotation is used), y the `junit.jupiter.tempdir.scope` configuration parameter to `per_context`. However, please note that this option is deprecated and will be removed in a future release. -`@TempDir` is not supported on constructor parameters. If you wish to retain a single -reference to a temp directory across lifecycle methods and the current test method, please -use field injection by annotating an instance field with `@TempDir`. - The following example stores a _shared_ temporary directory in a `static` field. This allows the same `sharedTempDir` to be used in all lifecycle methods and test methods of -the test class. For better isolation, you should use an instance field so that each test -method uses a separate directory. +the test class. For better isolation, you should use an instance field or constructor +injection so that each test method uses a separate directory. [source,java,indent=0] .A test class that shares a temporary directory across test methods @@ -2991,9 +3146,9 @@ include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_field_injectio ---- The `@TempDir` annotation has an optional `cleanup` attribute that can be set to either -`NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, temporary -directories are not deleted after a test completes. If it is set to `ON_SUCCESS`, -temporary directories are deleted only after a test completed successfully. +`NEVER`, `ON_SUCCESS`, or `ALWAYS`. If the cleanup mode is set to `NEVER`, the temporary +directory will not be deleted after the test completes. If it is set to `ON_SUCCESS`, the +temporary directory will only be deleted after the test if the test completed successfully. The default cleanup mode is `ALWAYS`. You can use the `junit.jupiter.tempdir.cleanup.mode.default` @@ -3029,7 +3184,7 @@ prefix instead of the `junit` constant value. include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_factory_name_prefix] ---- -It's also possible to use an in-memory file system like `{Jimfs}` for the creation of the +It is also possible to use an in-memory file system like `{Jimfs}` for the creation of the temporary directory. The following example demonstrates how to achieve that. [source,java,indent=0] @@ -3060,18 +3215,17 @@ include::{testDir}/example/TempDirectoryDemo.java[tags=user_guide_composed_annot Meta-annotations or additional annotations on the field or parameter the `TempDir` annotation is declared on might expose additional attributes to configure the factory. Such annotations and related attributes can be accessed via the `AnnotatedElementContext` -parameter of `createTempDirectory`. +parameter of the `createTempDirectory(...)` method. -You can use the `junit.jupiter.tempdir.factory.default` -<> to specify the fully qualified -class name of the `TempDirFactory` you would like to use by default. Just like for -factories configured via the `factory` attribute of the `@TempDir` annotation, -the supplied class has to implement the `TempDirFactory` interface. The default factory -will be used for all `@TempDir` annotations unless the `factory` attribute of the -annotation specifies a different factory. +You can use the `junit.jupiter.tempdir.factory.default` <> to specify the fully qualified class name of the +`TempDirFactory` you would like to use by default. Just like for factories configured via +the `factory` attribute of the `@TempDir` annotation, the supplied class has to implement +the `TempDirFactory` interface. The default factory will be used for all `@TempDir` +annotations unless the `factory` attribute of the annotation specifies a different factory. -In summary, the factory for a temporary directory is determined according to the -following precedence rules: +In summary, the factory for a temporary directory is determined according to the following +precedence rules: 1. The `factory` attribute of the `@TempDir` annotation, if present 2. The default `TempDirFactory` configured via the configuration diff --git a/documentation/src/main/java/example/domain/Person.java b/documentation/src/main/java/example/domain/Person.java index 00e376dabef8..5da8625c722e 100644 --- a/documentation/src/main/java/example/domain/Person.java +++ b/documentation/src/main/java/example/domain/Person.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/main/java/example/registration/WebClient.java b/documentation/src/main/java/example/registration/WebClient.java index 857259d85895..eceb7a528f7a 100644 --- a/documentation/src/main/java/example/registration/WebClient.java +++ b/documentation/src/main/java/example/registration/WebClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/main/java/example/registration/WebResponse.java b/documentation/src/main/java/example/registration/WebResponse.java index 329cd7445d6b..e6fb139cde19 100644 --- a/documentation/src/main/java/example/registration/WebResponse.java +++ b/documentation/src/main/java/example/registration/WebResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/main/java/example/registration/WebServerExtension.java b/documentation/src/main/java/example/registration/WebServerExtension.java index 0fded5e34735..fd552069a27b 100644 --- a/documentation/src/main/java/example/registration/WebServerExtension.java +++ b/documentation/src/main/java/example/registration/WebServerExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/main/java/example/util/Calculator.java b/documentation/src/main/java/example/util/Calculator.java index 37cffa666ef6..06ea606439a8 100644 --- a/documentation/src/main/java/example/util/Calculator.java +++ b/documentation/src/main/java/example/util/Calculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/main/java/example/util/ListWriter.java b/documentation/src/main/java/example/util/ListWriter.java index 84af14834e79..c2e822c3ac4c 100644 --- a/documentation/src/main/java/example/util/ListWriter.java +++ b/documentation/src/main/java/example/util/ListWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/main/java/example/util/StringUtils.java b/documentation/src/main/java/example/util/StringUtils.java index dba77b1c48ae..a48d72e3d3ad 100644 --- a/documentation/src/main/java/example/util/StringUtils.java +++ b/documentation/src/main/java/example/util/StringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/AssertionsDemo.java b/documentation/src/test/java/example/AssertionsDemo.java index 24d8c0691769..ef72db10079c 100644 --- a/documentation/src/test/java/example/AssertionsDemo.java +++ b/documentation/src/test/java/example/AssertionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -41,8 +41,9 @@ void standardAssertions() { assertEquals(2, calculator.add(1, 1)); assertEquals(4, calculator.multiply(2, 2), "The optional failure message is now the last parameter"); - assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- " - + "to avoid constructing complex messages unnecessarily."); + + // Lazily evaluates generateFailureMessage('a','b'). + assertTrue('a' < 'b', () -> generateFailureMessage('a','b')); } @Test @@ -160,6 +161,10 @@ private static String greeting() { return "Hello, World!"; } + private static String generateFailureMessage(char a, char b) { + return "Assertion messages can be lazily evaluated -- " + + "to avoid constructing complex messages unnecessarily." + (a < b); + } } // end::user_guide[] // @formatter:on diff --git a/documentation/src/test/java/example/AssumptionsDemo.java b/documentation/src/test/java/example/AssumptionsDemo.java index e9b106fc993e..91320b117913 100644 --- a/documentation/src/test/java/example/AssumptionsDemo.java +++ b/documentation/src/test/java/example/AssumptionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/AutoCloseDemo.java b/documentation/src/test/java/example/AutoCloseDemo.java index 054b1b1bcf0c..11e9c4778c04 100644 --- a/documentation/src/test/java/example/AutoCloseDemo.java +++ b/documentation/src/test/java/example/AutoCloseDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/BeforeAndAfterSuiteDemo.java b/documentation/src/test/java/example/BeforeAndAfterSuiteDemo.java new file mode 100644 index 000000000000..ee51c0d39577 --- /dev/null +++ b/documentation/src/test/java/example/BeforeAndAfterSuiteDemo.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +import org.junit.platform.suite.api.AfterSuite; +import org.junit.platform.suite.api.BeforeSuite; +import org.junit.platform.suite.api.SelectPackages; +import org.junit.platform.suite.api.Suite; + +//tag::user_guide[] +@Suite +@SelectPackages("example") +class BeforeAndAfterSuiteDemo { + + @BeforeSuite + static void beforeSuite() { + // executes before the test suite + } + + @AfterSuite + static void afterSuite() { + // executes after the test suite + } + +} +//end::user_guide[] diff --git a/documentation/src/test/java/example/ConditionalTestExecutionDemo.java b/documentation/src/test/java/example/ConditionalTestExecutionDemo.java index c2011653287a..ef18bb5ddff6 100644 --- a/documentation/src/test/java/example/ConditionalTestExecutionDemo.java +++ b/documentation/src/test/java/example/ConditionalTestExecutionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,9 +10,9 @@ package example; -import static org.junit.jupiter.api.condition.JRE.JAVA_10; import static org.junit.jupiter.api.condition.JRE.JAVA_11; -import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; import static org.junit.jupiter.api.condition.JRE.JAVA_9; import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.MAC; @@ -101,26 +101,26 @@ void notOnNewMacs() { // tag::user_guide_jre[] @Test - @EnabledOnJre(JAVA_8) - void onlyOnJava8() { + @EnabledOnJre(JAVA_17) + void onlyOnJava17() { // ... } @Test - @EnabledOnJre({ JAVA_9, JAVA_10 }) - void onJava9Or10() { + @EnabledOnJre({ JAVA_17, JAVA_21 }) + void onJava17And21() { // ... } @Test @EnabledForJreRange(min = JAVA_9, max = JAVA_11) - void fromJava9to11() { + void fromJava9To11() { // ... } @Test @EnabledForJreRange(min = JAVA_9) - void fromJava9toCurrentJavaFeatureNumber() { + void onJava9AndHigher() { // ... } @@ -138,23 +138,81 @@ void notOnJava9() { @Test @DisabledForJreRange(min = JAVA_9, max = JAVA_11) - void notFromJava9to11() { + void notFromJava9To11() { // ... } @Test @DisabledForJreRange(min = JAVA_9) - void notFromJava9toCurrentJavaFeatureNumber() { + void notOnJava9AndHigher() { // ... } @Test @DisabledForJreRange(max = JAVA_11) - void notFromJava8to11() { + void notFromJava8To11() { // ... } // end::user_guide_jre[] + // tag::user_guide_jre_arbitrary_versions[] + @Test + @EnabledOnJre(versions = 26) + void onlyOnJava26() { + // ... + } + + @Test + @EnabledOnJre(versions = { 25, 26 }) + // Can also be expressed as follows. + // @EnabledOnJre(value = JAVA_25, versions = 26) + void onJava25And26() { + // ... + } + + @Test + @EnabledForJreRange(minVersion = 26) + void onJava26AndHigher() { + // ... + } + + @Test + @EnabledForJreRange(minVersion = 25, maxVersion = 27) + // Can also be expressed as follows. + // @EnabledForJreRange(min = JAVA_25, maxVersion = 27) + void fromJava25To27() { + // ... + } + + @Test + @DisabledOnJre(versions = 26) + void notOnJava26() { + // ... + } + + @Test + @DisabledOnJre(versions = { 25, 26 }) + // Can also be expressed as follows. + // @DisabledOnJre(value = JAVA_25, versions = 26) + void notOnJava25And26() { + // ... + } + + @Test + @DisabledForJreRange(minVersion = 26) + void notOnJava26AndHigher() { + // ... + } + + @Test + @DisabledForJreRange(minVersion = 25, maxVersion = 27) + // Can also be expressed as follows. + // @DisabledForJreRange(min = JAVA_25, maxVersion = 27) + void notFromJava25To27() { + // ... + } + // end::user_guide_jre_arbitrary_versions[] + // tag::user_guide_native[] @Test @EnabledInNativeImage diff --git a/documentation/src/test/java/example/CustomLauncherInterceptor.java b/documentation/src/test/java/example/CustomLauncherInterceptor.java index a18653087f20..727fc15c9b93 100644 --- a/documentation/src/test/java/example/CustomLauncherInterceptor.java +++ b/documentation/src/test/java/example/CustomLauncherInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/CustomTestEngine.java b/documentation/src/test/java/example/CustomTestEngine.java index 7809d4c3ad3e..2a7a21c87f42 100644 --- a/documentation/src/test/java/example/CustomTestEngine.java +++ b/documentation/src/test/java/example/CustomTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/DisabledClassDemo.java b/documentation/src/test/java/example/DisabledClassDemo.java index 545dd5b4e152..12695bb07a6f 100644 --- a/documentation/src/test/java/example/DisabledClassDemo.java +++ b/documentation/src/test/java/example/DisabledClassDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/DisabledTestsDemo.java b/documentation/src/test/java/example/DisabledTestsDemo.java index 7f8c87f565e2..35c404e28997 100644 --- a/documentation/src/test/java/example/DisabledTestsDemo.java +++ b/documentation/src/test/java/example/DisabledTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/DisplayNameDemo.java b/documentation/src/test/java/example/DisplayNameDemo.java index 41ba41622ac0..8598c0fac040 100644 --- a/documentation/src/test/java/example/DisplayNameDemo.java +++ b/documentation/src/test/java/example/DisplayNameDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/DisplayNameGeneratorDemo.java b/documentation/src/test/java/example/DisplayNameGeneratorDemo.java index 79f2ab985038..db76b7a8e55f 100644 --- a/documentation/src/test/java/example/DisplayNameGeneratorDemo.java +++ b/documentation/src/test/java/example/DisplayNameGeneratorDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/DocumentationTestSuite.java b/documentation/src/test/java/example/DocumentationTestSuite.java index 6bd7ef87b81f..68a61f938af8 100644 --- a/documentation/src/test/java/example/DocumentationTestSuite.java +++ b/documentation/src/test/java/example/DocumentationTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/DynamicTestsDemo.java b/documentation/src/test/java/example/DynamicTestsDemo.java index eb31750af91b..32388f62ed7b 100644 --- a/documentation/src/test/java/example/DynamicTestsDemo.java +++ b/documentation/src/test/java/example/DynamicTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,6 @@ package example; // tag::user_guide[] - import static example.util.StringUtils.isPalindrome; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -19,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.Named.named; import java.util.Arrays; import java.util.Collection; @@ -34,8 +32,6 @@ import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Named; -import org.junit.jupiter.api.NamedExecutable; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.ThrowingConsumer; @@ -102,7 +98,7 @@ Stream dynamicTestsFromIntStream() { } @TestFactory - Stream generateRandomNumberOfTestsFromIterator() { + Stream generateRandomNumberOfTests() { // Generates random positive integers between 0 and 100 until // a number evenly divisible by 7 is encountered. @@ -154,51 +150,6 @@ Stream dynamicTestsFromStreamFactoryMethod() { return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor); } - @TestFactory - Stream dynamicTestsFromStreamFactoryMethodWithNames() { - // Stream of palindromes to check - Stream> inputStream = Stream.of( - named("racecar is a palindrome", "racecar"), - named("radar is also a palindrome", "radar"), - named("mom also seems to be a palindrome", "mom"), - named("dad is yet another palindrome", "dad") - ); - - // Returns a stream of dynamic tests. - return DynamicTest.stream(inputStream, - text -> assertTrue(isPalindrome(text))); - } - - @TestFactory - Stream dynamicTestsFromStreamFactoryMethodWithNamedExecutables() { - // Stream of palindromes to check - Stream inputStream = Stream.of("racecar", "radar", "mom", "dad") - .map(PalindromeNamedExecutable::new); - - // Returns a stream of dynamic tests based on NamedExecutables. - return DynamicTest.stream(inputStream); - } - - // Can be a record in Java 16 and later - static class PalindromeNamedExecutable implements NamedExecutable { - - private final String text; - - public PalindromeNamedExecutable(String text) { - this.text = text; - } - - @Override - public String getName() { - return String.format("'%s' is a palindrome", text); - } - - @Override - public void execute() { - assertTrue(isPalindrome(text)); - } - } - @TestFactory Stream dynamicTestsWithContainers() { return Stream.of("A", "B", "C") diff --git a/documentation/src/test/java/example/ExampleTestCase.java b/documentation/src/test/java/example/ExampleTestCase.java index fd9b7cf07ac8..afad21430bb6 100644 --- a/documentation/src/test/java/example/ExampleTestCase.java +++ b/documentation/src/test/java/example/ExampleTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/ExternalCustomConditionDemo.java b/documentation/src/test/java/example/ExternalCustomConditionDemo.java index 1f83a26d3b30..800ec253c998 100644 --- a/documentation/src/test/java/example/ExternalCustomConditionDemo.java +++ b/documentation/src/test/java/example/ExternalCustomConditionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/ExternalFieldSourceDemo.java b/documentation/src/test/java/example/ExternalFieldSourceDemo.java index 7918e5e5db02..b6bdc67d5364 100644 --- a/documentation/src/test/java/example/ExternalFieldSourceDemo.java +++ b/documentation/src/test/java/example/ExternalFieldSourceDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/ExternalMethodSourceDemo.java b/documentation/src/test/java/example/ExternalMethodSourceDemo.java index 9cc1a15ff7bf..6627180408b5 100644 --- a/documentation/src/test/java/example/ExternalMethodSourceDemo.java +++ b/documentation/src/test/java/example/ExternalMethodSourceDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/Fast.java b/documentation/src/test/java/example/Fast.java index 226d76949828..67cf49ad6c99 100644 --- a/documentation/src/test/java/example/Fast.java +++ b/documentation/src/test/java/example/Fast.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/FastTest.java b/documentation/src/test/java/example/FastTest.java index 00b5b28459c1..cccbc328d994 100644 --- a/documentation/src/test/java/example/FastTest.java +++ b/documentation/src/test/java/example/FastTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/HamcrestAssertionsDemo.java b/documentation/src/test/java/example/HamcrestAssertionsDemo.java index 4bfa2a05c9a1..bb678e06a1d3 100644 --- a/documentation/src/test/java/example/HamcrestAssertionsDemo.java +++ b/documentation/src/test/java/example/HamcrestAssertionsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/HttpServerDemo.java b/documentation/src/test/java/example/HttpServerDemo.java index d961793db5a0..6411ec5d19c4 100644 --- a/documentation/src/test/java/example/HttpServerDemo.java +++ b/documentation/src/test/java/example/HttpServerDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/IgnoredTestsDemo.java b/documentation/src/test/java/example/IgnoredTestsDemo.java index 4e559ea1b9e5..4718c922ca9f 100644 --- a/documentation/src/test/java/example/IgnoredTestsDemo.java +++ b/documentation/src/test/java/example/IgnoredTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/JUnit4Tests.java b/documentation/src/test/java/example/JUnit4Tests.java index e9ed6ed9581a..d915ef6e46e1 100644 --- a/documentation/src/test/java/example/JUnit4Tests.java +++ b/documentation/src/test/java/example/JUnit4Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/JUnitPlatformClassDemo.java b/documentation/src/test/java/example/JUnitPlatformClassDemo.java index 82e945d6c04b..bf776d627f6c 100644 --- a/documentation/src/test/java/example/JUnitPlatformClassDemo.java +++ b/documentation/src/test/java/example/JUnitPlatformClassDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java b/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java index 4e09c0fba9a5..259486cee3f8 100644 --- a/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java +++ b/documentation/src/test/java/example/JUnitPlatformSuiteDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java b/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java index 98b555230b75..12bf6db6dc37 100644 --- a/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java +++ b/documentation/src/test/java/example/MethodSourceParameterResolutionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java b/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java index 6566d55acc83..d40557dd30ac 100644 --- a/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java +++ b/documentation/src/test/java/example/MyFirstJUnitJupiterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java b/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java index b7a98414ae49..79717d4f159b 100644 --- a/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java +++ b/documentation/src/test/java/example/OrderedNestedTestClassesDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/OrderedTestsDemo.java b/documentation/src/test/java/example/OrderedTestsDemo.java index db90b9c6da80..4f677639e4dd 100644 --- a/documentation/src/test/java/example/OrderedTestsDemo.java +++ b/documentation/src/test/java/example/OrderedTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index 4d99f22c9311..2a9d0b77fb78 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -51,6 +51,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.params.ArgumentCountValidationMode; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; @@ -147,6 +148,14 @@ void testWithEnumSourceInclude(ChronoUnit unit) { } // end::EnumSource_include_example[] + // tag::EnumSource_range_example[] + @ParameterizedTest + @EnumSource(from = "HOURS", to = "DAYS") + void testWithEnumSourceRange(ChronoUnit unit) { + assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.HALF_DAYS, ChronoUnit.DAYS).contains(unit)); + } + // end::EnumSource_range_example[] + // tag::EnumSource_exclude_example[] @ParameterizedTest @EnumSource(mode = EXCLUDE, names = { "ERAS", "FOREVER" }) @@ -163,6 +172,15 @@ void testWithEnumSourceRegex(ChronoUnit unit) { } // end::EnumSource_regex_example[] + // tag::EnumSource_range_exclude_example[] + @ParameterizedTest + @EnumSource(from = "HOURS", to = "DAYS", mode = EXCLUDE, names = { "HALF_DAYS" }) + void testWithEnumSourceRangeExclude(ChronoUnit unit) { + assertTrue(EnumSet.of(ChronoUnit.HOURS, ChronoUnit.DAYS).contains(unit)); + assertFalse(EnumSet.of(ChronoUnit.HALF_DAYS).contains(unit)); + } + // end::EnumSource_range_exclude_example[] + // tag::simple_MethodSource_example[] @ParameterizedTest @MethodSource("stringProvider") @@ -294,10 +312,10 @@ void testWithMultiArgFieldSource(String str, int num, List list) { // tag::CsvSource_example[] @ParameterizedTest @CsvSource({ - "apple, 1", - "banana, 2", + "apple, 1", + "banana, 2", "'lemon, lime', 0xF1", - "strawberry, 700_000" + "strawberry, 700_000" }) void testWithCsvSource(String fruit, int rank) { assertNotNull(fruit); @@ -348,6 +366,29 @@ public Stream provideArguments(ExtensionContext context) { } // end::ArgumentsProvider_example[] + @ParameterizedTest + @ArgumentsSource(MyArgumentsProviderWithConstructorInjection.class) + void testWithArgumentsSourceWithConstructorInjection(String argument) { + assertNotNull(argument); + } + + static + // tag::ArgumentsProviderWithConstructorInjection_example[] + public class MyArgumentsProviderWithConstructorInjection implements ArgumentsProvider { + + private final TestInfo testInfo; + + public MyArgumentsProviderWithConstructorInjection(TestInfo testInfo) { + this.testInfo = testInfo; + } + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(Arguments.of(testInfo.getDisplayName())); + } + } + // end::ArgumentsProviderWithConstructorInjection_example[] + // tag::ParameterResolver_example[] @BeforeEach void beforeEach(TestInfo testInfo) { @@ -456,75 +497,77 @@ void testWithExplicitJavaTimeConverter( // @formatter:on // @formatter:off - // tag::ArgumentsAccessor_example[] - @ParameterizedTest - @CsvSource({ - "Jane, Doe, F, 1990-05-20", - "John, Doe, M, 1990-10-22" - }) - void testWithArgumentsAccessor(ArgumentsAccessor arguments) { - Person person = new Person(arguments.getString(0), - arguments.getString(1), - arguments.get(2, Gender.class), - arguments.get(3, LocalDate.class)); - - if (person.getFirstName().equals("Jane")) { - assertEquals(Gender.F, person.getGender()); - } - else { - assertEquals(Gender.M, person.getGender()); - } - assertEquals("Doe", person.getLastName()); - assertEquals(1990, person.getDateOfBirth().getYear()); - } - // end::ArgumentsAccessor_example[] + // tag::ArgumentsAccessor_example[] + @ParameterizedTest + @CsvSource({ + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + }) + void testWithArgumentsAccessor(ArgumentsAccessor arguments) { + Person person = new Person( + arguments.getString(0), + arguments.getString(1), + arguments.get(2, Gender.class), + arguments.get(3, LocalDate.class)); + + if (person.getFirstName().equals("Jane")) { + assertEquals(Gender.F, person.getGender()); + } + else { + assertEquals(Gender.M, person.getGender()); + } + assertEquals("Doe", person.getLastName()); + assertEquals(1990, person.getDateOfBirth().getYear()); + } + // end::ArgumentsAccessor_example[] // @formatter:on // @formatter:off - // tag::ArgumentsAggregator_example[] - @ParameterizedTest - @CsvSource({ - "Jane, Doe, F, 1990-05-20", - "John, Doe, M, 1990-10-22" - }) - void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) { - // perform assertions against person - } - - // end::ArgumentsAggregator_example[] - static - // tag::ArgumentsAggregator_example_PersonAggregator[] - public class PersonAggregator implements ArgumentsAggregator { - @Override - public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { - return new Person(arguments.getString(0), - arguments.getString(1), - arguments.get(2, Gender.class), - arguments.get(3, LocalDate.class)); - } - } - // end::ArgumentsAggregator_example_PersonAggregator[] + // tag::ArgumentsAggregator_example[] + @ParameterizedTest + @CsvSource({ + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + }) + void testWithArgumentsAggregator(@AggregateWith(PersonAggregator.class) Person person) { + // perform assertions against person + } + + // end::ArgumentsAggregator_example[] + static + // tag::ArgumentsAggregator_example_PersonAggregator[] + public class PersonAggregator implements ArgumentsAggregator { + @Override + public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) { + return new Person( + arguments.getString(0), + arguments.getString(1), + arguments.get(2, Gender.class), + arguments.get(3, LocalDate.class)); + } + } + // end::ArgumentsAggregator_example_PersonAggregator[] // @formatter:on // @formatter:off - // tag::ArgumentsAggregator_with_custom_annotation_example[] - @ParameterizedTest - @CsvSource({ - "Jane, Doe, F, 1990-05-20", - "John, Doe, M, 1990-10-22" - }) - void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { - // perform assertions against person - } - // end::ArgumentsAggregator_with_custom_annotation_example[] - - // tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @AggregateWith(PersonAggregator.class) - public @interface CsvToPerson { - } - // end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] + // tag::ArgumentsAggregator_with_custom_annotation_example[] + @ParameterizedTest + @CsvSource({ + "Jane, Doe, F, 1990-05-20", + "John, Doe, M, 1990-10-22" + }) + void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) { + // perform assertions against person + } + // end::ArgumentsAggregator_with_custom_annotation_example[] + + // tag::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + @AggregateWith(PersonAggregator.class) + public @interface CsvToPerson { + } + // end::ArgumentsAggregator_with_custom_annotation_example_CsvToPerson[] // @formatter:on // tag::custom_display_names[] @@ -584,4 +627,13 @@ static Stream otherProvider() { return Stream.of("bar"); } // end::repeatable_annotations[] + + @extensions.ExpectToFail + // tag::argument_count_validation[] + @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT) + @CsvSource({ "42, -666" }) + void testWithArgumentCountValidation(int number) { + assertTrue(number > 0); + } + // end::argument_count_validation[] } diff --git a/documentation/src/test/java/example/PollingTimeoutDemo.java b/documentation/src/test/java/example/PollingTimeoutDemo.java index 3f0b825369c9..58147b83c238 100644 --- a/documentation/src/test/java/example/PollingTimeoutDemo.java +++ b/documentation/src/test/java/example/PollingTimeoutDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/RepeatedTestsDemo.java b/documentation/src/test/java/example/RepeatedTestsDemo.java index f11f7a5521b0..dd23f1f39158 100644 --- a/documentation/src/test/java/example/RepeatedTestsDemo.java +++ b/documentation/src/test/java/example/RepeatedTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/SlowTests.java b/documentation/src/test/java/example/SlowTests.java index 0c3b6a22c4cf..93f341cf4545 100644 --- a/documentation/src/test/java/example/SlowTests.java +++ b/documentation/src/test/java/example/SlowTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/StandardTests.java b/documentation/src/test/java/example/StandardTests.java index 98e6a9cf35cf..d5ebbdc7ab5f 100644 --- a/documentation/src/test/java/example/StandardTests.java +++ b/documentation/src/test/java/example/StandardTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/SuiteDemo.java b/documentation/src/test/java/example/SuiteDemo.java index af7a1d386047..238c705d044a 100644 --- a/documentation/src/test/java/example/SuiteDemo.java +++ b/documentation/src/test/java/example/SuiteDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/TaggingDemo.java b/documentation/src/test/java/example/TaggingDemo.java index 5517bc2910e6..43e95e3d52c6 100644 --- a/documentation/src/test/java/example/TaggingDemo.java +++ b/documentation/src/test/java/example/TaggingDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/TempDirectoryDemo.java b/documentation/src/test/java/example/TempDirectoryDemo.java index 4998f939fd9b..e662f695698a 100644 --- a/documentation/src/test/java/example/TempDirectoryDemo.java +++ b/documentation/src/test/java/example/TempDirectoryDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -63,6 +63,7 @@ void copyFileFromSourceToTarget(@TempDir Path source, @TempDir Path target) thro } // end::user_guide_multiple_directories[] + @SuppressWarnings("JUnitMalformedDeclaration") static // tag::user_guide_field_injection[] class SharedTempDirectoryDemo { @@ -87,6 +88,7 @@ void anotherTestThatUsesTheSameTempDir() { } // end::user_guide_field_injection[] + @SuppressWarnings("JUnitMalformedDeclaration") static // tag::user_guide_cleanup_mode[] class CleanupModeDemo { @@ -99,6 +101,7 @@ void fileTest(@TempDir(cleanup = ON_SUCCESS) Path tempDir) { } // end::user_guide_cleanup_mode[] + @SuppressWarnings("JUnitMalformedDeclaration") static // tag::user_guide_factory_name_prefix[] class TempDirFactoryDemo { @@ -121,6 +124,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio } // end::user_guide_factory_name_prefix[] + @SuppressWarnings("JUnitMalformedDeclaration") static // tag::user_guide_factory_jimfs[] class InMemoryTempDirDemo { @@ -137,7 +141,7 @@ static class JimfsTempDirFactory implements TempDirFactory { @Override public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws IOException { - return Files.createTempDirectory(fileSystem.getPath("/"), "junit"); + return Files.createTempDirectory(fileSystem.getPath("/"), "junit-"); } @Override @@ -158,6 +162,7 @@ public void close() throws IOException { } // end::user_guide_composed_annotation[] + @SuppressWarnings("JUnitMalformedDeclaration") static // tag::user_guide_composed_annotation_usage[] class JimfsTempDirAnnotationDemo { diff --git a/documentation/src/test/java/example/TestInfoDemo.java b/documentation/src/test/java/example/TestInfoDemo.java index eae99ae63afc..d6b55e0bffff 100644 --- a/documentation/src/test/java/example/TestInfoDemo.java +++ b/documentation/src/test/java/example/TestInfoDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag; @@ -23,10 +24,16 @@ @DisplayName("TestInfo Demo") class TestInfoDemo { - TestInfoDemo(TestInfo testInfo) { + @BeforeAll + static void beforeAll(TestInfo testInfo) { assertEquals("TestInfo Demo", testInfo.getDisplayName()); } + TestInfoDemo(TestInfo testInfo) { + String displayName = testInfo.getDisplayName(); + assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()")); + } + @BeforeEach void init(TestInfo testInfo) { String displayName = testInfo.getDisplayName(); diff --git a/documentation/src/test/java/example/TestReporterDemo.java b/documentation/src/test/java/example/TestReporterDemo.java index 7d9a3f35c206..8c24a8c0a111 100644 --- a/documentation/src/test/java/example/TestReporterDemo.java +++ b/documentation/src/test/java/example/TestReporterDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,11 +10,17 @@ package example; +import static java.util.Collections.singletonList; + +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; @@ -41,5 +47,24 @@ void reportMultipleKeyValuePairs(TestReporter testReporter) { testReporter.publishEntry(values); } + @Test + void reportFiles(TestReporter testReporter, @TempDir Path tempDir) throws Exception { + + testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8, + file -> Files.write(file, singletonList("Test 1"))); + + Path existingFile = Files.write(tempDir.resolve("test2.txt"), singletonList("Test 2")); + testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8); + + testReporter.publishDirectory("test3", dir -> { + Files.write(dir.resolve("nested1.txt"), singletonList("Nested content 1")); + Files.write(dir.resolve("nested2.txt"), singletonList("Nested content 2")); + }); + + Path existingDir = Files.createDirectory(tempDir.resolve("test4")); + Files.write(existingDir.resolve("nested1.txt"), singletonList("Nested content 1")); + Files.write(existingDir.resolve("nested2.txt"), singletonList("Nested content 2")); + testReporter.publishDirectory(existingDir); + } } // end::user_guide[] diff --git a/documentation/src/test/java/example/TestTemplateDemo.java b/documentation/src/test/java/example/TestTemplateDemo.java index 35c3a9a8d6cf..1dda57c462e5 100644 --- a/documentation/src/test/java/example/TestTemplateDemo.java +++ b/documentation/src/test/java/example/TestTemplateDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/TestingAStackDemo.java b/documentation/src/test/java/example/TestingAStackDemo.java index d4f4a381eac6..80b1ec495d83 100644 --- a/documentation/src/test/java/example/TestingAStackDemo.java +++ b/documentation/src/test/java/example/TestingAStackDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/TimeoutDemo.java b/documentation/src/test/java/example/TimeoutDemo.java index 7be021c0982a..1311c477a5f4 100644 --- a/documentation/src/test/java/example/TimeoutDemo.java +++ b/documentation/src/test/java/example/TimeoutDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/UsingTheLauncherDemo.java b/documentation/src/test/java/example/UsingTheLauncherDemo.java index 3d0b06bb7995..6b0a2d508237 100644 --- a/documentation/src/test/java/example/UsingTheLauncherDemo.java +++ b/documentation/src/test/java/example/UsingTheLauncherDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java b/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java index cae7cc799975..14248fbca2f1 100644 --- a/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java +++ b/documentation/src/test/java/example/callbacks/AbstractDatabaseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java b/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java index ad4a13076536..c179021d3e71 100644 --- a/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java +++ b/documentation/src/test/java/example/callbacks/BrokenLifecycleMethodConfigDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java index 6cf730d1d2e1..5e2c1e89da2e 100644 --- a/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java +++ b/documentation/src/test/java/example/callbacks/DatabaseTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/callbacks/Extension1.java b/documentation/src/test/java/example/callbacks/Extension1.java index e258e6cde952..e1ee2e7fd7aa 100644 --- a/documentation/src/test/java/example/callbacks/Extension1.java +++ b/documentation/src/test/java/example/callbacks/Extension1.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/callbacks/Extension2.java b/documentation/src/test/java/example/callbacks/Extension2.java index 132234139389..45eb532d78e2 100644 --- a/documentation/src/test/java/example/callbacks/Extension2.java +++ b/documentation/src/test/java/example/callbacks/Extension2.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/callbacks/Logger.java b/documentation/src/test/java/example/callbacks/Logger.java index a77224d68ea0..e921ae11d025 100644 --- a/documentation/src/test/java/example/callbacks/Logger.java +++ b/documentation/src/test/java/example/callbacks/Logger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/defaultmethods/ComparableContract.java b/documentation/src/test/java/example/defaultmethods/ComparableContract.java index 7e0a0435b1fc..18cff47a02ab 100644 --- a/documentation/src/test/java/example/defaultmethods/ComparableContract.java +++ b/documentation/src/test/java/example/defaultmethods/ComparableContract.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/defaultmethods/EqualsContract.java b/documentation/src/test/java/example/defaultmethods/EqualsContract.java index c6c64ec8a9a6..ea1ddf3758a9 100644 --- a/documentation/src/test/java/example/defaultmethods/EqualsContract.java +++ b/documentation/src/test/java/example/defaultmethods/EqualsContract.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/defaultmethods/StringTests.java b/documentation/src/test/java/example/defaultmethods/StringTests.java index 128683a33ec0..219196703792 100644 --- a/documentation/src/test/java/example/defaultmethods/StringTests.java +++ b/documentation/src/test/java/example/defaultmethods/StringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/defaultmethods/Testable.java b/documentation/src/test/java/example/defaultmethods/Testable.java index cf0a6a308fc5..6b864d6ad1ca 100644 --- a/documentation/src/test/java/example/defaultmethods/Testable.java +++ b/documentation/src/test/java/example/defaultmethods/Testable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/AssertDoesNotThrowExceptionDemo.java b/documentation/src/test/java/example/exception/AssertDoesNotThrowExceptionDemo.java index 0c14624cc62f..29b2849c3b85 100644 --- a/documentation/src/test/java/example/exception/AssertDoesNotThrowExceptionDemo.java +++ b/documentation/src/test/java/example/exception/AssertDoesNotThrowExceptionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/ExceptionAssertionDemo.java b/documentation/src/test/java/example/exception/ExceptionAssertionDemo.java index c99776ea53a0..4e211b17c110 100644 --- a/documentation/src/test/java/example/exception/ExceptionAssertionDemo.java +++ b/documentation/src/test/java/example/exception/ExceptionAssertionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/ExceptionAssertionExactDemo.java b/documentation/src/test/java/example/exception/ExceptionAssertionExactDemo.java index 8ef8dfe7c404..51b62ec36e41 100644 --- a/documentation/src/test/java/example/exception/ExceptionAssertionExactDemo.java +++ b/documentation/src/test/java/example/exception/ExceptionAssertionExactDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/FailedAssertionDemo.java b/documentation/src/test/java/example/exception/FailedAssertionDemo.java index f12bba305e19..bf07cb706078 100644 --- a/documentation/src/test/java/example/exception/FailedAssertionDemo.java +++ b/documentation/src/test/java/example/exception/FailedAssertionDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java b/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java index 3546702086e6..428ba73c078e 100644 --- a/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java +++ b/documentation/src/test/java/example/exception/IgnoreIOExceptionExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java b/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java index 55ae4d88b9b5..840727c2b509 100644 --- a/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java +++ b/documentation/src/test/java/example/exception/IgnoreIOExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java b/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java index 81b7ed2b12db..aa32b2cc30b9 100644 --- a/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java +++ b/documentation/src/test/java/example/exception/MultipleHandlersTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package example.exception; -import static example.exception.MultipleHandlersTestCase.ThirdExecutedHandler; +import example.exception.MultipleHandlersTestCase.ThirdExecutedHandler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,38 +24,38 @@ @ExtendWith(ThirdExecutedHandler.class) class MultipleHandlersTestCase { - // Register handlers for @Test, @BeforeEach, @AfterEach only - @ExtendWith(SecondExecutedHandler.class) - @ExtendWith(FirstExecutedHandler.class) - @Test - void testMethod() { - } - - // end::user_guide[] - - static class FirstExecutedHandler implements TestExecutionExceptionHandler { - @Override - public void handleTestExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - throw ex; - } - } - - static class SecondExecutedHandler implements LifecycleMethodExecutionExceptionHandler { - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - throw ex; - } - } - - static class ThirdExecutedHandler implements LifecycleMethodExecutionExceptionHandler { - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - throw ex; - } - } + // Register handlers for @Test, @BeforeEach, @AfterEach only + @ExtendWith(SecondExecutedHandler.class) + @ExtendWith(FirstExecutedHandler.class) + @Test + void testMethod() { + } + + // end::user_guide[] + + static class FirstExecutedHandler implements TestExecutionExceptionHandler { + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + throw ex; + } + } + + static class SecondExecutedHandler implements LifecycleMethodExecutionExceptionHandler { + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + throw ex; + } + } + + static class ThirdExecutedHandler implements LifecycleMethodExecutionExceptionHandler { + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + throw ex; + } + } // tag::user_guide[] } // end::user_guide[] diff --git a/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java b/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java index 8e13f000a491..dafaf528eaf3 100644 --- a/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java +++ b/documentation/src/test/java/example/exception/RecordStateOnErrorExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,38 +17,37 @@ // tag::user_guide[] class RecordStateOnErrorExtension implements LifecycleMethodExecutionExceptionHandler { - @Override - public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during class setup"); - throw ex; - } - - @Override - public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during test setup"); - throw ex; - } - - @Override - public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during test cleanup"); - throw ex; - } - - @Override - public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex) - throws Throwable { - memoryDumpForFurtherInvestigation("Failure recorded during class cleanup"); - throw ex; - } - // end::user_guide[] - - private void memoryDumpForFurtherInvestigation(String error) { - - } + @Override + public void handleBeforeAllMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during class setup"); + throw ex; + } + + @Override + public void handleBeforeEachMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during test setup"); + throw ex; + } + + @Override + public void handleAfterEachMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during test cleanup"); + throw ex; + } + + @Override + public void handleAfterAllMethodExecutionException(ExtensionContext context, Throwable ex) + throws Throwable { + memoryDumpForFurtherInvestigation("Failure recorded during class cleanup"); + throw ex; + } + // end::user_guide[] + + private void memoryDumpForFurtherInvestigation(String error) { + } // tag::user_guide[] } // end::user_guide[] diff --git a/documentation/src/test/java/example/exception/UncaughtExceptionHandlingDemo.java b/documentation/src/test/java/example/exception/UncaughtExceptionHandlingDemo.java index ccc23d012c4d..f27ccba6a3b7 100644 --- a/documentation/src/test/java/example/exception/UncaughtExceptionHandlingDemo.java +++ b/documentation/src/test/java/example/exception/UncaughtExceptionHandlingDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/HttpServerExtension.java b/documentation/src/test/java/example/extensions/HttpServerExtension.java index 29318152228f..5ba23509d964 100644 --- a/documentation/src/test/java/example/extensions/HttpServerExtension.java +++ b/documentation/src/test/java/example/extensions/HttpServerExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/HttpServerResource.java b/documentation/src/test/java/example/extensions/HttpServerResource.java index 2f4e2e52afc3..845e88773fdc 100644 --- a/documentation/src/test/java/example/extensions/HttpServerResource.java +++ b/documentation/src/test/java/example/extensions/HttpServerResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java index c20c17ce9d86..7ed2942742c1 100644 --- a/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java +++ b/documentation/src/test/java/example/extensions/ParameterResolverConflictDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java index 8e68516ce659..44e3d60cb4f1 100644 --- a/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java +++ b/documentation/src/test/java/example/extensions/ParameterResolverCustomAnnotationDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java index c0b5b374c45d..7cf2963985e2 100644 --- a/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java +++ b/documentation/src/test/java/example/extensions/ParameterResolverCustomTypeDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java b/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java index 8f306a728632..7062d42c7ba1 100644 --- a/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java +++ b/documentation/src/test/java/example/extensions/ParameterResolverNoConflictDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/Random.java b/documentation/src/test/java/example/extensions/Random.java index ee4e32322630..c134fe50af91 100644 --- a/documentation/src/test/java/example/extensions/Random.java +++ b/documentation/src/test/java/example/extensions/Random.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/RandomNumberDemo.java b/documentation/src/test/java/example/extensions/RandomNumberDemo.java index 8ace575739c3..e11b87ce5b52 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberDemo.java +++ b/documentation/src/test/java/example/extensions/RandomNumberDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/extensions/RandomNumberExtension.java b/documentation/src/test/java/example/extensions/RandomNumberExtension.java index 2b16cc1c38b1..666e6ffe9ae5 100644 --- a/documentation/src/test/java/example/extensions/RandomNumberExtension.java +++ b/documentation/src/test/java/example/extensions/RandomNumberExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,17 +18,17 @@ import java.util.function.Predicate; import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.platform.commons.support.ModifierSupport; // end::user_guide[] // @formatter:off // tag::user_guide[] class RandomNumberExtension - implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { + implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver { private final java.util.Random random = new java.util.Random(System.nanoTime()); @@ -47,9 +47,8 @@ public void beforeAll(ExtensionContext context) { * {@code @Random} and can be assigned an integer value. */ @Override - public void beforeEach(ExtensionContext context) { + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { Class testClass = context.getRequiredTestClass(); - Object testInstance = context.getRequiredTestInstance(); injectFields(testClass, testInstance, ModifierSupport::isNotStatic); } diff --git a/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java b/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java index 326d3e02fdc4..348fd3607c1a 100644 --- a/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java +++ b/documentation/src/test/java/example/interceptor/SwingEdtInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/registration/DocumentationDemo.java b/documentation/src/test/java/example/registration/DocumentationDemo.java index 15971c249f1f..59571061d7de 100644 --- a/documentation/src/test/java/example/registration/DocumentationDemo.java +++ b/documentation/src/test/java/example/registration/DocumentationDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/registration/WebServerDemo.java b/documentation/src/test/java/example/registration/WebServerDemo.java index bd0f32587fdc..7064bad5cc7e 100644 --- a/documentation/src/test/java/example/registration/WebServerDemo.java +++ b/documentation/src/test/java/example/registration/WebServerDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java index bbf367cee897..8db5232d5bcb 100644 --- a/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java +++ b/documentation/src/test/java/example/session/GlobalSetupTeardownListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/session/HttpTests.java b/documentation/src/test/java/example/session/HttpTests.java index 564834f8260a..fdb560b66fa6 100644 --- a/documentation/src/test/java/example/session/HttpTests.java +++ b/documentation/src/test/java/example/session/HttpTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java new file mode 100644 index 000000000000..dd0fa9a5bcb4 --- /dev/null +++ b/documentation/src/test/java/example/sharedresources/ChildrenSharedResourcesDemo.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.sharedresources; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; +import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ResourceLock; + +// tag::user_guide[] +@Execution(CONCURRENT) +@ResourceLock(value = "a", mode = READ, target = CHILDREN) +public class ChildrenSharedResourcesDemo { + + @ResourceLock(value = "a", mode = READ_WRITE) + @Test + void test1() throws InterruptedException { + Thread.sleep(2000L); + } + + @Test + void test2() throws InterruptedException { + Thread.sleep(2000L); + } + + @Test + void test3() throws InterruptedException { + Thread.sleep(2000L); + } + + @Test + void test4() throws InterruptedException { + Thread.sleep(2000L); + } + + @Test + void test5() throws InterruptedException { + Thread.sleep(2000L); + } + +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java new file mode 100644 index 000000000000..4abe7297e357 --- /dev/null +++ b/documentation/src/test/java/example/sharedresources/DynamicSharedResourcesDemo.java @@ -0,0 +1,80 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example.sharedresources; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; +import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.ResourceLocksProvider; + +// tag::user_guide[] +@Execution(CONCURRENT) +@ResourceLock(providers = DynamicSharedResourcesDemo.Provider.class) +class DynamicSharedResourcesDemo { + + private Properties backup; + + @BeforeEach + void backup() { + backup = new Properties(); + backup.putAll(System.getProperties()); + } + + @AfterEach + void restore() { + System.setProperties(backup); + } + + @Test + void customPropertyIsNotSetByDefault() { + assertNull(System.getProperty("my.prop")); + } + + @Test + void canSetCustomPropertyToApple() { + System.setProperty("my.prop", "apple"); + assertEquals("apple", System.getProperty("my.prop")); + } + + @Test + void canSetCustomPropertyToBanana() { + System.setProperty("my.prop", "banana"); + assertEquals("banana", System.getProperty("my.prop")); + } + + static class Provider implements ResourceLocksProvider { + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + ResourceAccessMode mode = testMethod.getName().startsWith("canSet") ? READ_WRITE : READ; + return Collections.singleton(new Lock(SYSTEM_PROPERTIES, mode)); + } + } + +} +// end::user_guide[] diff --git a/documentation/src/test/java/example/SharedResourcesDemo.java b/documentation/src/test/java/example/sharedresources/StaticSharedResourcesDemo.java similarity index 93% rename from documentation/src/test/java/example/SharedResourcesDemo.java rename to documentation/src/test/java/example/sharedresources/StaticSharedResourcesDemo.java index d52248892356..efacccdf2094 100644 --- a/documentation/src/test/java/example/SharedResourcesDemo.java +++ b/documentation/src/test/java/example/sharedresources/StaticSharedResourcesDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package example; +package example.sharedresources; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -27,7 +27,7 @@ // tag::user_guide[] @Execution(CONCURRENT) -class SharedResourcesDemo { +class StaticSharedResourcesDemo { private Properties backup; diff --git a/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java b/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java index bfb8b9938fcb..5e059796b92e 100644 --- a/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java +++ b/documentation/src/test/java/example/testinterface/TestInterfaceDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java b/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java index 0b78dabc2660..77781ab34581 100644 --- a/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java +++ b/documentation/src/test/java/example/testinterface/TestInterfaceDynamicTestsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java index c7741aad38d6..ae54cd61c3cf 100644 --- a/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java +++ b/documentation/src/test/java/example/testinterface/TestLifecycleLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java b/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java index 57bd61ec4651..d4bce388f2f0 100644 --- a/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java +++ b/documentation/src/test/java/example/testinterface/TimeExecutionLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java index d7d5a4896057..2d5e632e67ca 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitAllEventsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java index 18b719fab536..fbbdae71c42c 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitFailedMethodDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java index 009d591c7e78..888325f893e3 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitSkippedMethodDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java b/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java index 44d41feeb244..5928f0fd7c20 100644 --- a/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java +++ b/documentation/src/test/java/example/testkit/EngineTestKitStatisticsDemo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/timing/TimingExtension.java b/documentation/src/test/java/example/timing/TimingExtension.java index ff1c9079abf9..4d12db567543 100644 --- a/documentation/src/test/java/example/timing/TimingExtension.java +++ b/documentation/src/test/java/example/timing/TimingExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/example/timing/TimingExtensionTests.java b/documentation/src/test/java/example/timing/TimingExtensionTests.java index fd04a5d60448..585284d877d9 100644 --- a/documentation/src/test/java/example/timing/TimingExtensionTests.java +++ b/documentation/src/test/java/example/timing/TimingExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/extensions/DisabledOnOpenJ9.java b/documentation/src/test/java/extensions/DisabledOnOpenJ9.java index f0a2a6206bd4..188b346a5e33 100644 --- a/documentation/src/test/java/extensions/DisabledOnOpenJ9.java +++ b/documentation/src/test/java/extensions/DisabledOnOpenJ9.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java/extensions/ExpectToFail.java b/documentation/src/test/java/extensions/ExpectToFail.java index adea1dcaeffa..eb148be26658 100644 --- a/documentation/src/test/java/extensions/ExpectToFail.java +++ b/documentation/src/test/java/extensions/ExpectToFail.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/java21/example/DynamicTestsNamedDemo.java b/documentation/src/test/java21/example/DynamicTestsNamedDemo.java new file mode 100644 index 000000000000..0d515a8b49bc --- /dev/null +++ b/documentation/src/test/java21/example/DynamicTestsNamedDemo.java @@ -0,0 +1,76 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] + +import static example.util.StringUtils.isPalindrome; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Named.named; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.NamedExecutable; +import org.junit.jupiter.api.TestFactory; + +public class DynamicTestsNamedDemo { + + @TestFactory + Stream dynamicTestsFromStreamFactoryMethodWithNames() { + // Stream of palindromes to check + // end::user_guide[] + // @formatter:off + // tag::user_guide[] + var inputStream = Stream.of( + named("racecar is a palindrome", "racecar"), + named("radar is also a palindrome", "radar"), + named("mom also seems to be a palindrome", "mom"), + named("dad is yet another palindrome", "dad") + ); + // end::user_guide[] + // @formatter:on + // tag::user_guide[] + + // Returns a stream of dynamic tests. + return DynamicTest.stream(inputStream, text -> assertTrue(isPalindrome(text))); + } + + @TestFactory + Stream dynamicTestsFromStreamFactoryMethodWithNamedExecutables() { + // Stream of palindromes to check + // end::user_guide[] + // @formatter:off + // tag::user_guide[] + var inputStream = Stream.of("racecar", "radar", "mom", "dad") + .map(PalindromeNamedExecutable::new); + // end::user_guide[] + // @formatter:on + // tag::user_guide[] + + // Returns a stream of dynamic tests based on NamedExecutables. + return DynamicTest.stream(inputStream); + } + + record PalindromeNamedExecutable(String text) implements NamedExecutable { + + @Override + public String getName() { + return String.format("'%s' is a palindrome", text); + } + + @Override + public void execute() { + assertTrue(isPalindrome(text)); + } + } +} +// end::user_guide[] diff --git a/documentation/src/test/java21/example/MyFirstJUnitJupiterRecordTests.java b/documentation/src/test/java21/example/MyFirstJUnitJupiterRecordTests.java new file mode 100644 index 000000000000..651862b1a1e5 --- /dev/null +++ b/documentation/src/test/java21/example/MyFirstJUnitJupiterRecordTests.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package example; + +// tag::user_guide[] +import static org.junit.jupiter.api.Assertions.assertEquals; + +import example.util.Calculator; + +import org.junit.jupiter.api.Test; + +record MyFirstJUnitJupiterRecordTests() { + + @Test + void addition() { + assertEquals(2, new Calculator().add(1, 1)); + } + +} +// end::user_guide[] diff --git a/documentation/src/test/kotlin/example/FibonacciCalculator.kt b/documentation/src/test/kotlin/example/FibonacciCalculator.kt index bcf2158f90de..224d7ed62f89 100644 --- a/documentation/src/test/kotlin/example/FibonacciCalculator.kt +++ b/documentation/src/test/kotlin/example/FibonacciCalculator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt b/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt index efd0566ac0d8..2abdde87e459 100644 --- a/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt +++ b/documentation/src/test/kotlin/example/KotlinAssertionsDemo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,8 @@ import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertAll import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.assertInstanceOf +import org.junit.jupiter.api.assertNotNull import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertTimeout import org.junit.jupiter.api.assertTimeoutPreemptively @@ -107,5 +109,29 @@ class KotlinAssertionsDemo { Thread.sleep(100) } } + + @Test + fun `assertNotNull with a smart cast`() { + val nullablePerson: Person? = person + + assertNotNull(nullablePerson) + + // The compiler smart casts nullablePerson to a non-nullable object. + // The safe call operator (?.) isn't required. + assertEquals(person.firstName, nullablePerson.firstName) + assertEquals(person.lastName, nullablePerson.lastName) + } + + @Test + fun `assertInstanceOf with a smart cast`() { + val maybePerson: Any = person + + assertInstanceOf(maybePerson) + + // The compiler smart casts maybePerson to a Person object, + // allowing to access the Person properties. + assertEquals(person.firstName, maybePerson.firstName) + assertEquals(person.lastName, maybePerson.lastName) + } } // end::user_guide[] diff --git a/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt b/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt index 65b729d7b2dd..4cbecd2b0660 100644 --- a/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt +++ b/documentation/src/test/kotlin/example/registration/KotlinWebServerDemo.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java b/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java index f822e6f8e935..4e08a529fbe1 100644 --- a/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java +++ b/documentation/src/tools/java/org/junit/api/tools/AbstractApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/ApiReport.java b/documentation/src/tools/java/org/junit/api/tools/ApiReport.java index 6478b10ad25d..148d7ef2ccde 100644 --- a/documentation/src/tools/java/org/junit/api/tools/ApiReport.java +++ b/documentation/src/tools/java/org/junit/api/tools/ApiReport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 e8e0df28b74d..02b7baa3f47d 100644 --- a/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java +++ b/documentation/src/tools/java/org/junit/api/tools/ApiReportGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/ApiReportWriter.java b/documentation/src/tools/java/org/junit/api/tools/ApiReportWriter.java index 65487727f694..c59a903d9551 100644 --- a/documentation/src/tools/java/org/junit/api/tools/ApiReportWriter.java +++ b/documentation/src/tools/java/org/junit/api/tools/ApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/AsciidocApiReportWriter.java b/documentation/src/tools/java/org/junit/api/tools/AsciidocApiReportWriter.java index 3c4f35fc624b..24ff7b9eab1a 100644 --- a/documentation/src/tools/java/org/junit/api/tools/AsciidocApiReportWriter.java +++ b/documentation/src/tools/java/org/junit/api/tools/AsciidocApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/Declaration.java b/documentation/src/tools/java/org/junit/api/tools/Declaration.java index 8cde00674801..a6921cce8862 100644 --- a/documentation/src/tools/java/org/junit/api/tools/Declaration.java +++ b/documentation/src/tools/java/org/junit/api/tools/Declaration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/HtmlApiReportWriter.java b/documentation/src/tools/java/org/junit/api/tools/HtmlApiReportWriter.java index dc042e183b78..56020962d54a 100644 --- a/documentation/src/tools/java/org/junit/api/tools/HtmlApiReportWriter.java +++ b/documentation/src/tools/java/org/junit/api/tools/HtmlApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/documentation/src/tools/java/org/junit/api/tools/MarkdownApiReportWriter.java b/documentation/src/tools/java/org/junit/api/tools/MarkdownApiReportWriter.java index 75798266fc3c..385d27d9ae07 100644 --- a/documentation/src/tools/java/org/junit/api/tools/MarkdownApiReportWriter.java +++ b/documentation/src/tools/java/org/junit/api/tools/MarkdownApiReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/gradle.properties b/gradle.properties index b5817893f904..4f2ab7df9946 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ group = org.junit -version = 5.11.0-SNAPSHOT +version = 5.12.0-SNAPSHOT jupiterGroup = org.junit.jupiter platformGroup = org.junit.platform -platformVersion = 1.11.0-SNAPSHOT +platformVersion = 1.12.0-SNAPSHOT vintageGroup = org.junit.vintage -vintageVersion = 5.11.0-SNAPSHOT +vintageVersion = 5.12.0-SNAPSHOT # We need more metaspace due to apparent memory leak in Asciidoctor/JRuby # The exports are needed due to https://github.com/diffplug/spotless/issues/834 @@ -17,9 +17,9 @@ org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryEr --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \ --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED -org.gradle.configuration-cache=true org.gradle.caching=true org.gradle.parallel=true +org.gradle.configuration-cache.parallel=true org.gradle.java.installations.fromEnv=JDK8,JDK18,JDK19,JDK20,JDK21,JDK22,JDK23,JDK24 org.gradle.kotlin.dsl.allWarningsAsErrors=true diff --git a/gradle/base/code-generator-model/src/main/resources/jre.yaml b/gradle/base/code-generator-model/src/main/resources/jre.yaml index ed1bdd3d59fc..1747ffa12dd6 100644 --- a/gradle/base/code-generator-model/src/main/resources/jre.yaml +++ b/gradle/base/code-generator-model/src/main/resources/jre.yaml @@ -28,3 +28,5 @@ since: '5.11' - version: 24 since: '5.11' +- version: 25 + since: '5.11.4' diff --git a/gradle/config/checkstyle/checkstyleNohttp.xml b/gradle/config/checkstyle/checkstyleNohttp.xml new file mode 100644 index 000000000000..d1723372016d --- /dev/null +++ b/gradle/config/checkstyle/checkstyleNohttp.xml @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/gradle/config/spotless/eclipse-public-license-2.0.java b/gradle/config/spotless/eclipse-public-license-2.0.java index 010f99b9800a..14bbeb94e39a 100644 --- a/gradle/config/spotless/eclipse-public-license-2.0.java +++ b/gradle/config/spotless/eclipse-public-license-2.0.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties deleted file mode 100644 index 63e5bbdf4845..000000000000 --- a/gradle/gradle-daemon-jvm.properties +++ /dev/null @@ -1,2 +0,0 @@ -#This file is generated by updateDaemonJvm -toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f001a3e516eb..66bfcb1f626b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,22 +1,24 @@ [versions] -ant = "1.10.14" +ant = "1.10.15" apiguardian = "1.1.2" -asciidoctorj-pdf = "2.3.17" -asciidoctor-plugins = "4.0.3" # Check if workaround in documentation.gradle.kts can be removed when upgrading -assertj = "3.26.3" -bnd = "7.0.0" -checkstyle = "10.17.0" -eclipse = "4.32.0" -jackson = "2.17.2" +asciidoctorj-pdf = "2.3.19" +asciidoctor-plugins = "4.0.4" # Check if workaround in documentation.gradle.kts can be removed when upgrading +assertj = "3.27.3" +bnd = "7.1.0" +checkstyle = "10.21.2" +eclipse = "4.34.0" +jackson = "2.18.2" jacoco = "0.8.12" jmh = "1.37" junit4 = "4.13.2" junit4Min = "4.12" -ktlint = "1.3.1" -log4j = "2.23.1" +ktlint = "1.5.0" +log4j = "2.24.3" +logback = "1.5.16" +mockito = "5.15.2" opentest4j = "1.3.0" -openTestReporting = "0.1.0-M2" -surefire = "3.3.1" +openTestReporting = "0.2.0-M2" +surefire = "3.5.2" xmlunit = "2.10.0" [libraries] @@ -24,16 +26,18 @@ ant = { module = "org.apache.ant:ant", version.ref = "ant" } ant-junit = { module = "org.apache.ant:ant-junit", version.ref = "ant" } ant-junitlauncher = { module = "org.apache.ant:ant-junitlauncher", version.ref = "ant" } apiguardian = { module = "org.apiguardian:apiguardian-api", version.ref = "apiguardian" } + +# 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.3.0" } + assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } -bartholdy = { module = "de.sormuras:bartholdy", version = "0.2.3" } bndlib = { module = "biz.aQute.bnd:biz.aQute.bndlib", version.ref = "bnd" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } -classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.174" } -commons-io = { module = "commons-io:commons-io", version = "2.16.1" } -groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.22" } -groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.21" } -hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" } +classgraph = { module = "io.github.classgraph:classgraph", version = "4.8.179" } +commons-io = { module = "commons-io:commons-io", version = "2.18.0" } +groovy4 = { module = "org.apache.groovy:groovy", version = "4.0.25" } +groovy2-bom = { module = "org.codehaus.groovy:groovy-bom", version = "2.5.23" } +hamcrest = { module = "org.hamcrest:hamcrest", version = "3.0" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jfrPolyfill = { module = "org.gradle.jfr.polyfill:jfr-polyfill", version = "1.0.2" } @@ -42,25 +46,30 @@ jimfs = { module = "com.google.jimfs:jimfs", version = "1.3.0" } jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" } joox = { module = "org.jooq:joox", version = "2.0.1" } -jte = { module = "gg.jte:jte", version = "3.1.12" } +jte = { module = "gg.jte:jte", version = "3.1.16" } junit4 = { module = "junit:junit", version = { require = "[4.12,)", prefer = "4.13.2" } } -kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.8.1" } +kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.1" } log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j-jul = { module = "org.apache.logging.log4j:log4j-jul", version.ref = "log4j" } -maven = { module = "org.apache.maven:apache-maven", version = "3.9.8" } +maven = { module = "org.apache.maven:apache-maven", version = "3.9.9" } mavenSurefirePlugin = { module = "org.apache.maven.plugins:maven-surefire-plugin", version.ref = "surefire" } -memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.0" } -mockito = { module = "org.mockito:mockito-junit-jupiter", version = "5.12.0" } +memoryfilesystem = { module = "com.github.marschall:memoryfilesystem", version = "2.8.1" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } +mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } +nohttp-checkstyle = { module = "io.spring.nohttp:nohttp-checkstyle", version = "0.0.11" } opentest4j = { module = "org.opentest4j:opentest4j", version.ref = "opentest4j" } +openTestReporting-cli = { module = "org.opentest4j.reporting:open-test-reporting-cli", version.ref = "openTestReporting" } openTestReporting-events = { module = "org.opentest4j.reporting:open-test-reporting-events", version.ref = "openTestReporting" } -openTestReporting-tooling = { module = "org.opentest4j.reporting:open-test-reporting-tooling", version.ref = "openTestReporting" } +openTestReporting-tooling-core = { module = "org.opentest4j.reporting:open-test-reporting-tooling-core", version.ref = "openTestReporting" } +openTestReporting-tooling-spi = { module = "org.opentest4j.reporting:open-test-reporting-tooling-spi", version.ref = "openTestReporting" } picocli = { module = "info.picocli:picocli", version = "4.7.6" } -slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.13" } +slf4j-julBinding = { module = "org.slf4j:slf4j-jdk14", version = "2.0.16" } spock1 = { module = "org.spockframework:spock-core", version = "1.3-groovy-2.5" } -univocity-parsers = { module = "com.univocity:univocity-parsers", version = "2.9.1" } +univocity-parsers = { module = "com.sonofab1rd:univocity-parsers", version = "2.10.1" } xmlunit-assertj = { module = "org.xmlunit:xmlunit-assertj3", version.ref = "xmlunit" } xmlunit-placeholders = { module = "org.xmlunit:xmlunit-placeholders", version.ref = "xmlunit" } testingAnnotations = { module = "com.gradle:develocity-testing-annotations", version = "2.0.1" } +woodstox = { module = "com.fasterxml.woodstox:woodstox-core", version = "7.1.0" } # Only declared here so Dependabot knows when to update the referenced versions asciidoctorj-pdf = { module = "org.asciidoctor:asciidoctorj-pdf", version.ref = "asciidoctorj-pdf" } @@ -68,6 +77,7 @@ eclipse-platform = { module = "org.eclipse.platform:org.eclipse.platform", versi jacoco = { module = "org.jacoco:jacoco", version.ref = "jacoco" } junit4-latest = { module = "junit:junit", version.ref = "junit4" } junit4-bundle = { module = "org.apache.servicemix.bundles:org.apache.servicemix.bundles.junit", version = "4.13.2_1" } +logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" } ktlint-cli = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } [bundles] @@ -80,14 +90,13 @@ asciidoctorConvert = { id = "org.asciidoctor.jvm.convert", version.ref = "asciid asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-plugins" } bnd = { id = "biz.aQute.bnd", version.ref = "bnd" } buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" } -commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.0.2" } -develocity = { id = "com.gradle.develocity", version = "3.17.5" } -foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.8.0" } -gitPublish = { id = "org.ajoberstar.git-publish", version = "4.2.2" } -jmh = { id = "me.champeau.jmh", version = "0.7.2" } +commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.1" } +develocity = { id = "com.gradle.develocity", version = "3.19.1" } +foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.9.0" } +gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.0" } +jmh = { id = "me.champeau.jmh", version = "0.7.3" } nexusPublish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } -nohttp = { id = "io.spring.nohttp", version = "0.0.11" } -plantuml = { id = "io.freefair.plantuml", version = "8.6" } -shadow = { id = "io.github.goooler.shadow", version = "8.1.8" } -spotless = { id = "com.diffplug.spotless", version = "7.0.0.BETA1" } -versions = { id = "com.github.ben-manes.versions", version = "0.51.0" } +plantuml = { id = "io.freefair.plantuml", version = "8.12.1" } +shadow = { id = "com.gradleup.shadow", version = "8.3.6" } +spotless = { id = "com.diffplug.spotless", version = "6.25.0" } +versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } diff --git a/gradle/plugins/build-parameters/build.gradle.kts b/gradle/plugins/build-parameters/build.gradle.kts index c9c2fa31ff0a..5d647a81a7de 100644 --- a/gradle/plugins/build-parameters/build.gradle.kts +++ b/gradle/plugins/build-parameters/build.gradle.kts @@ -41,6 +41,11 @@ buildParameters { description = "Whether or not to use Predictive Test Selection for selecting tests to execute" defaultValue = true } + bool("selectRemainingTests") { + // see https://docs.gradle.com/develocity/predictive-test-selection/#gradle-selection-mode + description = "Whether or not to use PTS' 'remaining tests' selection mode" + defaultValue = false + } } group("testDistribution") { bool("enabled") { @@ -62,7 +67,7 @@ buildParameters { description = "Testing related parameters" bool("enableJaCoCo") { description = "Enables JaCoCo test coverage reporting" - defaultValue = false + defaultValue = true } bool("enableJFR") { description = "Enables Java Flight Recorder functionality" @@ -71,6 +76,10 @@ buildParameters { integer("retries") { description = "Configures the number of times failing test are retried" } + bool("hideOpenTestReportHtmlGeneratorOutput") { + description = "Whether or not to hide the output of the OpenTestReportHtmlGenerator" + defaultValue = true + } } group("publishing") { bool("signArtifacts") { diff --git a/gradle/plugins/code-generator/build.gradle.kts b/gradle/plugins/code-generator/build.gradle.kts index 69f88347f7d6..e9f2ef657e47 100644 --- a/gradle/plugins/code-generator/build.gradle.kts +++ b/gradle/plugins/code-generator/build.gradle.kts @@ -1,15 +1,11 @@ plugins { - `kotlin-dsl` -} - -repositories { - gradlePluginPortal() + `kotlin-dsl` } dependencies { - implementation("junitbuild.base:code-generator-model") - implementation(projects.common) - implementation(libs.jackson.dataformat.yaml) - implementation(libs.jackson.module.kotlin) - implementation(libs.jte) + implementation("junitbuild.base:code-generator-model") + implementation(projects.common) + implementation(libs.jackson.dataformat.yaml) + implementation(libs.jackson.module.kotlin) + implementation(libs.jte) } 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 7b61c9609e78..59d9fa36ff43 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,13 +1,13 @@ import junitbuild.generator.GenerateJreRelatedSourceCode plugins { - java + java } val templates by sourceSets.registering dependencies { - add(templates.get().compileOnlyConfigurationName, dependencyFromLibs("jte")) - add(templates.get().compileOnlyConfigurationName, "junitbuild.base:code-generator-model") + add(templates.get().compileOnlyConfigurationName, dependencyFromLibs("jte")) + add(templates.get().compileOnlyConfigurationName, "junitbuild.base:code-generator-model") } val license: License by rootProject.extra @@ -16,20 +16,20 @@ val generateCode by tasks.registering sourceSets.named { it != templates.name }.configureEach { - val sourceSetName = name - val sourceSetTargetDir = rootTargetDir.map { it.dir(sourceSetName) } + val sourceSetName = name + val sourceSetTargetDir = rootTargetDir.map { it.dir(sourceSetName) } - val task = tasks.register(getTaskName("generateJreRelated", "SourceCode"), GenerateJreRelatedSourceCode::class) { - templateDir.convention(layout.dir(templates.map { - it.resources.srcDirs.single().resolve(sourceSetName) - })) - targetDir.convention(sourceSetTargetDir) - licenseHeaderFile.convention(license.headerFile) - } + val task = tasks.register(getTaskName("generateJreRelated", "SourceCode"), GenerateJreRelatedSourceCode::class) { + templateDir.convention(layout.dir(templates.map { + it.resources.srcDirs.single().resolve(sourceSetName) + })) + targetDir.convention(sourceSetTargetDir) + licenseHeaderFile.convention(license.headerFile) + } - java.srcDir(files(sourceSetTargetDir).builtBy(task)) + java.srcDir(files(sourceSetTargetDir).builtBy(task)) - generateCode { - dependsOn(task) - } + generateCode { + dependsOn(task) + } } diff --git a/gradle/plugins/common/build.gradle.kts b/gradle/plugins/common/build.gradle.kts index 0a93c96c0fee..6015360a7105 100644 --- a/gradle/plugins/common/build.gradle.kts +++ b/gradle/plugins/common/build.gradle.kts @@ -2,10 +2,6 @@ plugins { `kotlin-dsl` } -repositories { - gradlePluginPortal() -} - dependencies { implementation(projects.buildParameters) implementation(kotlin("gradle-plugin")) diff --git a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt b/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt index fc8a515977e7..33c7a591b546 100644 --- a/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt +++ b/gradle/plugins/common/src/main/kotlin/ProjectExtensions.kt @@ -1,10 +1,19 @@ import org.gradle.api.Project +import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.artifacts.VersionCatalog import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.the val Project.javaModuleName: String - get() = "org." + this.name.replace('-', '.') + get() = toModuleName(name) + +val ProjectDependency.javaModuleName: String + get() = toModuleName(name) + +private fun toModuleName(name: String) = "org.${name.replace('-', '.')}" + +fun Project.dependencyProject(dependency: ProjectDependency) = + project(dependency.path) fun Project.requiredVersionFromLibs(name: String) = libsVersionCatalog.findVersion(name).get().requiredVersion diff --git a/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt b/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt new file mode 100644 index 000000000000..8d106218b896 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/VersionExtensions.kt @@ -0,0 +1 @@ +fun Any.isSnapshot(): Boolean = toString().contains("SNAPSHOT") 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 new file mode 100644 index 000000000000..ae6f47ef34d7 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-conventions.gradle.kts @@ -0,0 +1,13 @@ +plugins { + base + checkstyle +} + +checkstyle { + toolVersion = requiredVersionFromLibs("checkstyle") + configDirectory = rootProject.layout.projectDirectory.dir("gradle/config/checkstyle") +} + +tasks.check { + dependsOn(tasks.withType()) +} 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 new file mode 100644 index 000000000000..641002f94ad2 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.checkstyle-nohttp.gradle.kts @@ -0,0 +1,36 @@ +plugins { + id("junitbuild.checkstyle-conventions") +} + +dependencies { + checkstyle(dependencyFromLibs("nohttp-checkstyle")) +} + +configurations.checkstyle { + resolutionStrategy { + eachDependency { + // Workaround for CVE-2024-12798 and CVE-2024-12801 + if (requested.group == "ch.qos.logback") { + useVersion(requiredVersionFromLibs("logback")) + } + } + } +} + +tasks.register("checkstyleNohttp") { + group = "verification" + description = "Checks for illegal uses of http://" + classpath = files(configurations.checkstyle) + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleNohttp.xml")) + source = fileTree(layout.projectDirectory) { + exclude(".git/**", "**/.gradle/**") + exclude(".idea/**", ".eclipse/**") + exclude("**/*.class") + exclude("**/*.hprof") + exclude("**/*.jar") + exclude("**/*.jpg", "**/*.png") + exclude("**/*.jks") + exclude("**/build/**") + exclude("**/.kotlin") + } +} 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 8a5ee778c3dc..b14973e05474 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 @@ -10,9 +10,9 @@ plugins { `java-library` eclipse idea - checkstyle id("junitbuild.base-conventions") id("junitbuild.build-parameters") + id("junitbuild.checkstyle-conventions") id("junitbuild.jacoco-java-conventions") } @@ -120,6 +120,17 @@ if (project in mavenizedProjects) { } } + if (!project.version.isSnapshot()) { + configurations { + compileClasspath { + resolutionStrategy.failOnChangingVersions() + } + runtimeClasspath { + resolutionStrategy.failOnChangingVersions() + } + } + } + } else { tasks { jar { @@ -162,15 +173,15 @@ val allMainClasses by tasks.registering { } val prepareModuleSourceDir by tasks.registering(Sync::class) { - from(moduleSourceDir) - from(sourceSets.named { it.startsWith("main") }.map { it.allJava }) - into(combinedModuleSourceDir.map { it.dir(javaModuleName) }) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(moduleSourceDir) + from(sourceSets.named { it.startsWith("main") }.map { it.allJava }) + into(combinedModuleSourceDir.map { it.dir(javaModuleName) }) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } val compileModule by tasks.registering(JavaCompile::class) { dependsOn(allMainClasses) - enabled = project in modularProjects + enabled = project in modularProjects source = fileTree(combinedModuleSourceDir).builtBy(prepareModuleSourceDir) destinationDirectory = moduleOutputDir sourceCompatibility = "9" @@ -184,27 +195,34 @@ val compileModule by tasks.registering(JavaCompile::class) { "--module-version", "${project.version}", )) - val moduleOptions = objects.newInstance(ModuleCompileOptions::class) - extensions.add("moduleOptions", moduleOptions) - moduleOptions.modulePath.from(configurations.compileClasspath) + val moduleOptions = objects.newInstance(ModuleCompileOptions::class) + extensions.add("moduleOptions", moduleOptions) + moduleOptions.modulePath.from(configurations.compileClasspath) options.compilerArgumentProviders.add(objects.newInstance(ModulePathArgumentProvider::class, project, combinedModuleSourceDir, modularProjects).apply { - modulePath.from(moduleOptions.modulePath) - }) + modulePath.from(moduleOptions.modulePath) + }) options.compilerArgumentProviders.addAll(modularProjects.map { objects.newInstance(PatchModuleArgumentProvider::class, project, it) }) modularity.inferModulePath = false - doFirst { - options.allCompilerArgs.forEach { - logger.info(it) - } - } + doFirst { + options.allCompilerArgs.forEach { + logger.info(it) + } + } } tasks.withType().configureEach { from(rootDir) { - include("LICENSE.md", "LICENSE-notice.md") + include("LICENSE.md") + into("META-INF") + } + from(rootDir) { + include("NOTICE.md") + rename { + "LICENSE-notice.md" + } into("META-INF") } val suffix = archiveClassifier.getOrElse("") @@ -308,23 +326,18 @@ afterEvaluate { } } -checkstyle { - toolVersion = requiredVersionFromLibs("checkstyle") - configDirectory = rootProject.layout.projectDirectory.dir("gradle/config/checkstyle") -} - tasks { checkstyleMain { - config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } checkstyleTest { - config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } } pluginManager.withPlugin("java-test-fixtures") { tasks.named("checkstyleTestFixtures") { - config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) } tasks.named("compileTestFixturesJava") { options.release = extension.testJavaVersion.majorVersion.toInt() diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts index 31b2325cb0bd..83b2a8e8e273 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-sources.gradle.kts @@ -30,7 +30,7 @@ listOf(9, 17).forEach { javaVersion -> } named("checkstyle${sourceSet.name.capitalized()}").configure { - config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) } if (project in mavenizedProjects) { diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-test-sources.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-test-sources.gradle.kts new file mode 100644 index 000000000000..4dd191dbe0b0 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-multi-release-test-sources.gradle.kts @@ -0,0 +1,42 @@ +import junitbuild.extensions.capitalized + +plugins { + id("junitbuild.java-library-conventions") +} + +listOf(21).forEach { javaVersion -> + val sourceSet = sourceSets.register("testRelease${javaVersion}") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + java { + setSrcDirs(setOf("src/test/java${javaVersion}")) + } + } + + configurations { + named(sourceSet.get().compileClasspathConfigurationName).configure { + extendsFrom(configurations.testCompileClasspath.get()) + } + named(sourceSet.get().runtimeClasspathConfigurationName).configure { + extendsFrom(configurations.testRuntimeClasspath.get()) + } + } + + tasks { + val testTask = register("testRelease${javaVersion}") { + group = "verification" + description = "Runs the tests for Java ${javaVersion}" + testClassesDirs = sourceSet.get().output.classesDirs + classpath = sourceSet.get().runtimeClasspath + } + check { + dependsOn(testTask) + } + named(sourceSet.get().compileJavaTaskName).configure { + options.release = javaVersion + } + named("checkstyle${sourceSet.name.capitalized()}").configure { + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleTest.xml")) + } + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts index 50f023226a69..873ff64cfb1e 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.java-toolchain-conventions.gradle.kts @@ -9,18 +9,18 @@ project.pluginManager.withPlugin("java") { val javaLanguageVersion = buildParameters.javaToolchain.version.map { JavaLanguageVersion.of(it) }.getOrElse(defaultLanguageVersion) val jvmImplementation = buildParameters.javaToolchain.implementation.map { when(it) { - "j9" -> JvmImplementation.J9 - else -> throw InvalidUserDataException("Unsupported JDK implementation: $it") - } + "j9" -> JvmImplementation.J9 + else -> throw InvalidUserDataException("Unsupported JDK implementation: $it") + } }.getOrElse(JvmImplementation.VENDOR_SPECIFIC) val extension = the() val javaToolchainService = the() extension.toolchain { - languageVersion = javaLanguageVersion - implementation = jvmImplementation - } + languageVersion = javaLanguageVersion + implementation = jvmImplementation + } pluginManager.withPlugin("org.jetbrains.kotlin.jvm") { configure { @@ -32,6 +32,17 @@ project.pluginManager.withPlugin("java") { tasks.withType().configureEach { javaLauncher = javaToolchainService.launcherFor(extension.toolchain) + if (javaLanguageVersion != defaultLanguageVersion) { + // Track exact version of Java to detect changes in behavior between EA builds sooner + inputs.property("javaRuntimeVersion", javaLauncher.get().metadata.javaRuntimeVersion) + } + } + + tasks.withType().configureEach { + if (javaLanguageVersion != defaultLanguageVersion) { + // Track exact version of Java to detect changes in behavior between EA builds sooner + inputs.property("javaRuntimeVersion", javaLauncher.get().metadata.javaRuntimeVersion) + } } tasks.withType().configureEach { 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 795f4684f5ab..a1d529652650 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,23 +1,19 @@ plugins { - id("me.champeau.jmh") + id("me.champeau.jmh") } jmh { - jmhVersion = requiredVersionFromLibs("jmh") + jmhVersion = requiredVersionFromLibs("jmh") } dependencies { - jmh(dependencyFromLibs("jmh-core")) - jmhAnnotationProcessor(dependencyFromLibs("jmh-generator-annprocess")) -} - -tasks.jmhJar { - notCompatibleWithConfigurationCache("https://github.com/melix/jmh-gradle-plugin/issues/229") + jmh(dependencyFromLibs("jmh-core")) + jmhAnnotationProcessor(dependencyFromLibs("jmh-generator-annprocess")) } pluginManager.withPlugin("checkstyle") { - tasks.named("checkstyleJmh").configure { - // use same style rules as defined for tests - config = resources.text.fromFile(project.the().configDirectory.file("checkstyleTest.xml")) - } + tasks.named("checkstyleJmh").configure { + // use same style rules as defined for tests + config = resources.text.fromFile(project.the().configDirectory.file("checkstyleTest.xml")) + } } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts index 0dcf7bf13282..046cf094dfad 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.kotlin-library-conventions.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.dsl.KotlinVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -10,13 +12,13 @@ tasks.named("kotlinSourcesJar") { } tasks.withType().configureEach { - kotlinOptions { - apiVersion = "1.6" - languageVersion = "1.6" + compilerOptions { + apiVersion = KotlinVersion.fromVersion("1.6") + languageVersion = apiVersion allWarningsAsErrors = false // Compiler arg is required for Kotlin 1.6 and below // see https://kotlinlang.org/docs/whatsnew17.html#stable-opt-in-requirements - freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") } } @@ -24,10 +26,10 @@ afterEvaluate { val extension = project.the() tasks { withType().configureEach { - kotlinOptions.jvmTarget = extension.mainJavaVersion.toString() + compilerOptions.jvmTarget = JvmTarget.fromTarget(extension.mainJavaVersion.toString()) } named("compileTestKotlin") { - kotlinOptions.jvmTarget = extension.testJavaVersion.toString() + compilerOptions.jvmTarget = JvmTarget.fromTarget(extension.testJavaVersion.toString()) } } } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts new file mode 100644 index 000000000000..262535f2083a --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.native-image-properties.gradle.kts @@ -0,0 +1,55 @@ +import junitbuild.graalvm.NativeImagePropertiesExtension +import java.util.zip.ZipFile + +plugins { + `java-library` +} + +val extension = extensions.create("nativeImageProperties").apply { + val resourceFile: RegularFile = layout.projectDirectory.file("src/nativeImage/initialize-at-build-time") + if (resourceFile.asFile.exists()) { + initializeAtBuildTime.convention(providers.fileContents(resourceFile).asText.map { it.trim().lines() }) + } else { + initializeAtBuildTime.empty() + } + initializeAtBuildTime.finalizeValueOnRead() +} + +val outputDir = layout.buildDirectory.dir("resources/nativeImage") + +val propertyFileTask = tasks.register("nativeImageProperties") { + destinationFile = outputDir.map { it.file("META-INF/native-image/${project.group}/${project.name}/native-image.properties") } + // see https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/#configuration-file-format + property("Args", extension.initializeAtBuildTime.map { + if (it.isEmpty()) { + "" + } else { + "--initialize-at-build-time=${it.joinToString(",")}" + } + }) +} + +val validationTask = tasks.register("validateNativeImageProperties") { + dependsOn(tasks.jar) + doLast { + val zipEntries = ZipFile(tasks.jar.get().archiveFile.get().asFile).use { zipFile -> + zipFile.entries().asSequence().map { it.name }.filter { it.endsWith(".class") }.toSet() + } + val missingClasses = extension.initializeAtBuildTime.get().filter { className -> + !zipEntries.contains("${className.replace('.', '/')}.class") + } + if (missingClasses.isNotEmpty()) { + throw GradleException("The following classes were specified as initialize-at-build-time but do not exist (you should probably remove them from nativeImageProperties.initializeAtBuildTime):\n${missingClasses.joinToString("\n- ", "- ")}") + } + } +} + +tasks.check { + dependsOn(validationTask) +} + +sourceSets { + main { + output.dir(propertyFileTask.map { outputDir }) + } +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts index 86ee27db2373..d1adaa09ff68 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.osgi-conventions.gradle.kts @@ -111,7 +111,6 @@ val verifyOSGi by tasks.registering(Resolve::class) { // end up in the dependencies of those projects. bundles(osgiVerificationClasspath) properties.empty() - outputs.doNotCacheIf("https://github.com/bndtools/bnd/issues/5666") { true } } tasks.check { 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 ad877035e1f3..90146181e380 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 @@ -5,8 +5,6 @@ plugins { id("junitbuild.build-parameters") } -val isSnapshot = project.version.toString().contains("SNAPSHOT") - val jupiterProjects: List by rootProject val platformProjects: List by rootProject val vintageProjects: List by rootProject @@ -40,7 +38,7 @@ tasks.withType().configureEach { dependsOn(tasks.build) } -val signArtifacts = buildParameters.publishing.signArtifacts.getOrElse(!(isSnapshot || buildParameters.ci)) +val signArtifacts = buildParameters.publishing.signArtifacts.getOrElse(!(project.version.isSnapshot() || buildParameters.ci)) signing { useGpgCmd() diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts index 5d7d95d6b1b4..ed1732e00e78 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.shadow-conventions.gradle.kts @@ -2,7 +2,7 @@ import junitbuild.java.ModuleCompileOptions plugins { id("junitbuild.java-library-conventions") - id("io.github.goooler.shadow") + id("com.gradleup.shadow") } val shadowed = configurations.dependencyScope("shadowed") @@ -71,7 +71,7 @@ tasks { classpath -= sourceSets.main.get().output classpath += files(shadowJar.map { it.archiveFile }) } - named("compileModule") { - the().modulePath.from(shadowedClasspath.get()) - } + named("compileModule") { + the().modulePath.from(shadowedClasspath.get()) + } } 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 b0b9cfc9314d..12a1d64470b0 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 @@ -7,8 +7,8 @@ val license: License by rootProject.extra spotless { format("misc") { - target("*.gradle.kts", "buildSrc/**/*.gradle.kts", "*.gitignore") - targetExclude("buildSrc/build/**") + target("*.gradle.kts", "gradle/plugins/**/*.gradle.kts", "*.gitignore") + targetExclude("gradle/plugins/**/build/**") indentWithTabs() trimTrailingWhitespace() endWithNewline() @@ -16,25 +16,34 @@ spotless { format("documentation") { target("*.adoc", "*.md", "src/**/*.adoc", "src/**/*.md") - targetExclude("**/build", "**/target") trimTrailingWhitespace() endWithNewline() } pluginManager.withPlugin("java") { - val configDir = rootProject.layout.projectDirectory.dir("gradle/config/eclipse") - val importOrderConfigFile = configDir.file("junit-eclipse.importorder") + val configDir = rootProject.layout.projectDirectory.dir("gradle/config/eclipse") + val importOrderConfigFile = configDir.file("junit-eclipse.importorder") val javaFormatterConfigFile = configDir.file("junit-eclipse-formatter-settings.xml") java { - licenseHeaderFile(license.headerFile, "(package|import|open|module) ") + licenseHeaderFile(license.headerFile, "(package|import) ") importOrderFile(importOrderConfigFile) val fullVersion = requiredVersionFromLibs("eclipse") val majorMinorVersion = "([0-9]+\\.[0-9]+).*".toRegex().matchEntire(fullVersion)!!.let { it.groups[1]!!.value } eclipse(majorMinorVersion).configFile(javaFormatterConfigFile) trimTrailingWhitespace() endWithNewline() + removeUnusedImports() + } + + format("moduleDescriptor") { + target(fileTree(layout.projectDirectory.dir("src/module")) { + include("**/module-info.java") + }) + licenseHeaderFile(license.headerFile, "^$") + trimTrailingWhitespace() + endWithNewline() } } @@ -46,5 +55,32 @@ spotless { trimTrailingWhitespace() endWithNewline() } + configurations.named { it.startsWith("spotless") }.configureEach { + // Workaround for CVE-2024-12798 and CVE-2024-12801 + resolutionStrategy { + eachDependency { + if (requested.group == "ch.qos.logback") { + useVersion(requiredVersionFromLibs("logback")) + } + } + } + } + } + + pluginManager.withPlugin("groovy") { + groovy { + licenseHeaderFile(license.headerFile) + trimTrailingWhitespace() + endWithNewline() + } + } +} + +tasks { + named("spotlessDocumentation") { + outputs.doNotCacheIf("negative avoidance savings") { true } + } + named("spotlessMisc") { + outputs.doNotCacheIf("negative avoidance savings") { true } } } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts b/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts index f60def6c4df2..3f48f5d656d7 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts +++ b/gradle/plugins/common/src/main/kotlin/junitbuild.temp-maven-repo.gradle.kts @@ -1,4 +1,5 @@ import junitbuild.extensions.capitalized +import junitbuild.release.VerifyBinaryArtifactsAreIdentical val tempRepoName by extra("temp") val tempRepoDir by extra { @@ -12,6 +13,10 @@ val clearTempRepoDir by tasks.registering { } } +val verifyArtifactsInStagingRepositoryAreReproducible by tasks.registering(VerifyBinaryArtifactsAreIdentical::class) { + localRepoDir.set(tempRepoDir) +} + subprojects { pluginManager.withPlugin("maven-publish") { configure { @@ -22,10 +27,13 @@ subprojects { } } } - tasks.withType().configureEach { - if (name.endsWith("To${tempRepoName.capitalized()}Repository")) { - dependsOn(clearTempRepoDir) - } + val publishingTasks = tasks.withType() + .named { it.endsWith("To${tempRepoName.capitalized()}Repository") } + publishingTasks.configureEach { + dependsOn(clearTempRepoDir) + } + verifyArtifactsInStagingRepositoryAreReproducible { + dependsOn(publishingTasks) } } } 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 15d694efbb58..5ec321f5f5d4 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,13 +1,64 @@ + import com.gradle.develocity.agent.gradle.internal.test.PredictiveTestSelectionConfigurationInternal +import com.gradle.develocity.agent.gradle.test.PredictiveTestSelectionMode +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 import org.gradle.internal.os.OperatingSystem +import java.io.OutputStream plugins { `java-library` id("junitbuild.build-parameters") } +var javaAgent = configurations.dependencyScope("javaAgent") +var javaAgentClasspath = configurations.resolvable("javaAgentClasspath") { + extendsFrom(javaAgent.get()) +} + +var openTestReportingCli = configurations.dependencyScope("openTestReportingCli") +var openTestReportingCliClasspath = configurations.resolvable("openTestReportingCliClasspath") { + extendsFrom(openTestReportingCli.get()) +} + +val generateOpenTestHtmlReport by tasks.registering(JavaExec::class) { + mustRunAfter(tasks.withType()) + mainClass.set("org.opentest4j.reporting.cli.ReportingCli") + args("html-report") + classpath(openTestReportingCliClasspath) + argumentProviders += objects.newInstance(HtmlReportParameters::class).apply { + eventXmlFiles.from(tasks.withType().map { + objects.fileTree() + .from(it.reports.junitXml.outputLocation) + .include("junit-*/open-test-report.xml") + }) + outputLocation = layout.buildDirectory.file("reports/open-test-report.html") + } + if (buildParameters.testing.hideOpenTestReportHtmlGeneratorOutput) { + standardOutput = object : OutputStream() { + override fun write(b: Int) { + // discard output + } + } + } + outputs.cacheIf { true } +} + +abstract class HtmlReportParameters : CommandLineArgumentProvider { + + @get:InputFiles + @get:PathSensitive(RELATIVE) + @get:SkipWhenEmpty + abstract val eventXmlFiles: ConfigurableFileCollection + + @get:OutputFile + abstract val outputLocation: RegularFileProperty + + override fun asArguments() = listOf("--output", outputLocation.get().asFile.absolutePath) + + eventXmlFiles.map { it.absolutePath }.toList() +} + tasks.withType().configureEach { useJUnitPlatform { includeEngines("junit-jupiter") @@ -36,12 +87,20 @@ tasks.withType().configureEach { predictiveTestSelection { enabled = buildParameters.junit.develocity.predictiveTestSelection.enabled + if (buildParameters.junit.develocity.predictiveTestSelection.selectRemainingTests) { + mode = PredictiveTestSelectionMode.REMAINING_TESTS + } + // Ensure PTS works when publishing Build Scans to scans.gradle.com this as PredictiveTestSelectionConfigurationInternal server = uri("https://ge.junit.org") + + mergeCodeCoverage = true } } systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") + // https://github.com/gradle/gradle/issues/30554 + systemProperty("log4j2.julLoggerAdapter", "org.apache.logging.log4j.jul.CoreLoggerAdapter") // Avoid overhead (see https://logging.apache.org/log4j/2.x/manual/jmx.html#enabling-jmx) systemProperty("log4j2.disableJmx", "true") // Required until ASM officially supports the JDK 14 @@ -67,19 +126,33 @@ tasks.withType().configureEach { jvmArgumentProviders += CommandLineArgumentProvider { listOf( "-Djunit.platform.reporting.open.xml.enabled=true", - "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}" + "-Djunit.platform.reporting.output.dir=${reports.junitXml.outputLocation.get().asFile.absolutePath}/junit-{uniqueNumber}", ) } + systemProperty("junit.platform.output.capture.stdout", "true") + systemProperty("junit.platform.output.capture.stderr", "true") + + jvmArgumentProviders += objects.newInstance(JavaAgentArgumentProvider::class).apply { + classpath.from(javaAgentClasspath) + } + + val reportDirTree = objects.fileTree().from(reports.junitXml.outputLocation) + doFirst { + reportDirTree.visit { + if (name.startsWith("junit-")) { + file.deleteRecursively() + } + } + } + + finalizedBy(generateOpenTestHtmlReport) } dependencies { testImplementation(dependencyFromLibs("assertj")) - testImplementation(dependencyFromLibs("mockito")) + testImplementation(dependencyFromLibs("mockito-junit-jupiter")) testImplementation(dependencyFromLibs("testingAnnotations")) - - if (!project.name.startsWith("junit-jupiter")) { - testImplementation(project(":junit-jupiter")) - } + testImplementation(project(":junit-jupiter")) testRuntimeOnly(project(":junit-platform-engine")) testRuntimeOnly(project(":junit-platform-jfr")) @@ -92,4 +165,20 @@ dependencies { testRuntimeOnly(dependencyFromLibs("openTestReporting-events")) { because("it's required to run tests via IntelliJ which does not consumed the shadowed jar of junit-platform-reporting") } + + openTestReportingCli(dependencyFromLibs("openTestReporting-cli")) + openTestReportingCli(project(":junit-platform-reporting")) + + javaAgent(dependencyFromLibs("mockito-core")) { + isTransitive = false + } +} + +abstract class JavaAgentArgumentProvider : CommandLineArgumentProvider { + + @get:Classpath + abstract val classpath: ConfigurableFileCollection + + override fun asArguments() = listOf("-javaagent:${classpath.singleFile.absolutePath}") + } 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 a92f31195ad2..896e76090c6f 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/exec/RunConsoleLauncher.kt @@ -6,7 +6,13 @@ import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import org.gradle.jvm.toolchain.JavaLauncher import org.gradle.jvm.toolchain.JavaToolchainService @@ -16,7 +22,6 @@ import org.gradle.process.CommandLineArgumentProvider import org.gradle.process.ExecOperations import trackOperationSystemAsInput import java.io.ByteArrayOutputStream -import java.util.* import javax.inject.Inject @CacheableTask @@ -97,4 +102,13 @@ abstract class RunConsoleLauncher @Inject constructor(private val execOperations debugging.set(enabled) } + @Suppress("unused") + @Option( + option = "show-output", + description = "Show output" + ) + fun setShowOutput(showOutput: Boolean) { + hideOutput.set(!showOutput) + } + } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt new file mode 100644 index 000000000000..48cb4042c439 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/graalvm/NativeImagePropertiesExtension.kt @@ -0,0 +1,7 @@ +package junitbuild.graalvm + +import org.gradle.api.provider.SetProperty + +abstract class NativeImagePropertiesExtension { + abstract val initializeAtBuildTime: SetProperty +} diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt index 282a6aeaf469..07acb7e488e4 100644 --- a/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/java/UpdateJarAction.kt @@ -2,7 +2,7 @@ package junitbuild.java import org.gradle.api.Action import org.gradle.api.Task -import org.gradle.api.internal.file.archive.ZipCopyAction +import org.gradle.api.internal.file.archive.ZipEntryConstants import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.jvm.toolchain.JavaLauncher @@ -19,7 +19,7 @@ abstract class UpdateJarAction @Inject constructor(private val operations: ExecO // Since ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES is in the default time zone (see its Javadoc), // we're converting it to the same time in UTC here to make the jar reproducible regardless of the // build's time zone. - private val CONSTANT_TIME_FOR_ZIP_ENTRIES = LocalDateTime.ofInstant(Instant.ofEpochMilli(ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES), ZoneId.systemDefault()) + private val CONSTANT_TIME_FOR_ZIP_ENTRIES = LocalDateTime.ofInstant(Instant.ofEpochMilli(ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES), ZoneId.systemDefault()) .toInstant(ZoneOffset.UTC) .toString() } diff --git a/gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt b/gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt new file mode 100644 index 000000000000..9acd67b96c89 --- /dev/null +++ b/gradle/plugins/common/src/main/kotlin/junitbuild/release/VerifyBinaryArtifactsAreIdentical.kt @@ -0,0 +1,80 @@ +package junitbuild.release + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import java.io.File +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse.BodyHandlers + +abstract class VerifyBinaryArtifactsAreIdentical : DefaultTask() { + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val localRepoDir: DirectoryProperty + + @get:Input + abstract val remoteRepoUrl: Property + + init { + // Depends on contents of remote repository + outputs.upToDateWhen { false } + } + + @Suppress("unused") + @Option( + option = "remote-repo-url", + description = "The URL of the remote repository to compare the local repository against" + ) + fun remoteRepo(url: String) { + remoteRepoUrl.set(url) + } + + @TaskAction + fun execute() { + val localRootDir = localRepoDir.get().asFile + val baseUrl = remoteRepoUrl.get() + val mismatches = mutableListOf() + var numChecks = 0 + HttpClient.newHttpClient().use { httpClient -> + localRootDir.walk().forEach { file -> + if (file.isFile && file.name.endsWith(".jar.sha512") && !file.name.endsWith("-javadoc.jar.sha512")) { + val localSha512 = file.readText() + 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 response = httpClient.send(request, BodyHandlers.ofString()) + val remoteSha512 = if (response.statusCode() == 200) response.body() else "status=${response.statusCode()}" + if (localSha512 != remoteSha512) { + mismatches.add(Mismatch(relativeFile, localSha512, remoteSha512)) + } + numChecks++ + } + } + } + require(numChecks > 0) { + "No files found to compare" + } + require(mismatches.isEmpty()) { + "The following files have different SHA-512 checksums in the local and remote repositories:\n\n" + + mismatches.joinToString("\n\n") { + """ + ${it.file} + local: ${it.localSha512} + remote: ${it.remoteSha512} + """.trimIndent() + } + } + } + + private data class Mismatch(val file: File, val localSha512: String, val remoteSha512: String) +} diff --git a/gradle/plugins/settings.gradle.kts b/gradle/plugins/settings.gradle.kts index 728290a2cad6..163866db2805 100644 --- a/gradle/plugins/settings.gradle.kts +++ b/gradle/plugins/settings.gradle.kts @@ -1,9 +1,18 @@ +val expectedJavaVersion = JavaVersion.VERSION_21 +val actualJavaVersion = JavaVersion.current() +require(actualJavaVersion == expectedJavaVersion) { + "The JUnit 5 build must be executed with Java ${expectedJavaVersion.majorVersion}. Currently executing with Java ${actualJavaVersion.majorVersion}." +} + dependencyResolutionManagement { versionCatalogs { create("libs") { from(files("../libs.versions.toml")) } } + repositories { + gradlePluginPortal() + } } rootProject.name = "plugins" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c3521197d7c..a4b76b9530d6 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 68e8816d71c9..d71047787f80 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6b11..f3b75f3b0d4f 100755 --- a/gradlew +++ b/gradlew @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/junit-jupiter-api/junit-jupiter-api.gradle.kts b/junit-jupiter-api/junit-jupiter-api.gradle.kts index bdf4c359550f..378fc86a3d5d 100644 --- a/junit-jupiter-api/junit-jupiter-api.gradle.kts +++ b/junit-jupiter-api/junit-jupiter-api.gradle.kts @@ -1,6 +1,7 @@ plugins { id("junitbuild.kotlin-library-conventions") id("junitbuild.code-generator") + id("junitbuild.native-image-properties") `java-test-fixtures` } @@ -15,6 +16,8 @@ dependencies { compileOnly(kotlin("stdlib")) + testFixturesImplementation(libs.assertj) + osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java index 7cbbfd7b0f15..171f530fb324 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java index bf1d90853f33..b46258fb5ad2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AfterEach.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java index 29b1218ff060..ae5f2828c795 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java index 7da0accd15b4..06be401319d6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertArrayEquals.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java index e83f4508daaf..e2b9135dbd88 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertDoesNotThrow.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java index 558d9afc8147..94afc3fe49c7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertEquals.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java index 83dad97da5ce..f7582574166d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertFalse.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java index 60d9b278e5ee..4d3197ba46fe 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertInstanceOf.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java index 820b5365e2b3..c0dcf1dd5875 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertIterableEquals.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java index ef25f47e1dd3..7596aed881f0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertLinesMatch.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java index 4bdd3376248c..1ccc41b7bb13 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotEquals.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java index 8d63cd76bdd6..f5fb9b849230 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java index 606934f317ca..860371937076 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNotSame.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java index 9d674d8c59c0..a81e7925415f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertNull.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java index 8e9278d19c84..f006dab823ff 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertSame.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java index 02947d1637af..70ee8b02fe4f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrows.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java index 830dca520260..4fa017318589 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertThrowsExactly.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java index 64b625b6c7fa..3aadf60f88af 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeout.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java index 4585bb0e8927..82b0148780a9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTimeoutPreemptively.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -113,7 +113,7 @@ private static T resolveFutureAndHandleException(Future cause = new ExecutionTimeoutException("Execution timed out in thread " + thread.getName()); cause.setStackTrace(thread.getStackTrace()); } - throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause); + throw failureFactory.createTimeoutFailure(timeout, messageSupplier, cause, thread); } catch (ExecutionException ex) { throw throwAsUncheckedException(ex.getCause()); @@ -124,7 +124,7 @@ private static T resolveFutureAndHandleException(Future } private static AssertionFailedError createAssertionFailure(Duration timeout, Supplier messageSupplier, - Throwable cause) { + Throwable cause, Thread thread) { return assertionFailure() // .message(messageSupplier) // .reason("execution timed out after " + timeout.toMillis() + " ms") // diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java index 0cb0bbc245bd..af43e0613263 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertTrue.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java index 5f826a88cb8d..ab65c8048db7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionFailureBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -115,7 +115,7 @@ public AssertionFailureBuilder actual(Object actual) { * failure message. * * @param includeValuesInMessage whether to include the actual and expected - * values + * values * @return this builder for method chaining */ public AssertionFailureBuilder includeValuesInMessage(boolean includeValuesInMessage) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java index 3ac27f22b1dd..9431e30b3940 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AssertionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java index 3f5df08971a0..cd037a416914 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -1493,7 +1493,7 @@ public static void assertArrayEquals(Object[] expected, Object[] actual, Supplie * iterators must return equal elements in the same order as each other. Note: * this means that the iterables do not need to be of the same type. Example:
{@code
 	 * import static java.util.Arrays.asList;
-	 *  . . .
+	 *  ...
 	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
 	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
 	 * assertIterableEquals(i0, i1); // Passes
@@ -1516,7 +1516,7 @@ public static void assertIterableEquals(Iterable expected, Iterable actual
 	 * elements in the same order as each other. Note: this means that the iterables
 	 * do not need to be of the same type. Example: 
{@code
 	 * import static java.util.Arrays.asList;
-	 *  . . .
+	 *  ...
 	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
 	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
 	 * assertIterableEquals(i0, i1); // Passes
@@ -1540,7 +1540,7 @@ public static void assertIterableEquals(Iterable expected, Iterable actual
 	 * elements in the same order as each other. Note: this means that the iterables
 	 * do not need to be of the same type. Example: 
{@code
 	 * import static java.util.Arrays.asList;
-	 *  . . .
+	 *  ...
 	 * Iterable i0 = new ArrayList<>(asList(1, 2, 3));
 	 * Iterable i1 = new LinkedList<>(asList(1, 2, 3));
 	 * assertIterableEquals(i0, i1); // Passes
@@ -3662,6 +3662,6 @@ public interface TimeoutFailureFactory {
 		 *
 		 * @return timeout failure; never {@code null}
 		 */
-		T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause);
+		T createTimeoutFailure(Duration timeout, Supplier messageSupplier, Throwable cause, Thread testThread);
 	}
 }
diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java
index f1ade0746475..6545518b4d3e 100644
--- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java
+++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assumptions.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-2024 the original author or authors.
+ * 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
@@ -27,7 +27,12 @@
  *
  * 

In direct contrast to failed {@linkplain Assertions assertions}, * failed assumptions do not result in a test failure; rather, - * a failed assumption results in a test being aborted. + * a failed assumption results in a test being aborted. However, + * failed assertions and other exceptions thrown by tests take precedence over + * failed assumptions when both are thrown during the execution of a test + * (for example, by different lifecycle methods), regardless of the order they + * are thrown in. In such cases, the test will be reported as failed + * rather than aborted. * *

Assumptions are typically used whenever it does not make sense to * continue execution of a given test method — for example, if the diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AutoClose.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AutoClose.java index 0a29faba7a95..f0bbc4646cc7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AutoClose.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/AutoClose.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java index 197556e7ad3b..e327653c46c3 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeAll.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java index 7aee562c0ac8..0f3058e8b12e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/BeforeEach.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java index d6c53bee156c..1dc3f7105596 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java index e27c05abb926..224c07fd3a8a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -90,8 +90,7 @@ public interface ClassOrderer { *

 	 * public void orderClasses(ClassOrdererContext context) {
 	 *     Collections.shuffle(context.getClassDescriptors());
-	 * }
-	 * 
+ * }
* * @param context the {@code ClassOrdererContext} containing the * {@linkplain ClassDescriptor class descriptors} to order; never {@code null} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java index 9d9d0325cdb9..bcc7e7ce402e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/ClassOrdererContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java index 44ec76fca882..e02c23cf030e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Disabled.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java index abc398327f15..c8318e1bfb3b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayName.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java index 5345e4d7305e..3a23b9a8ea72 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGeneration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java index f396ce850c7d..9852c79bffd5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DisplayNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,18 +10,23 @@ package org.junit.jupiter.api; +import static java.util.Collections.emptyList; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.commons.support.ModifierSupport.isStatic; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; import java.lang.reflect.Method; +import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; /** * {@code DisplayNameGenerator} defines the SPI for generating display names @@ -73,7 +78,8 @@ public interface DisplayNameGenerator { /** * Generate a display name for the given top-level or {@code static} nested test class. * - *

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

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

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

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

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

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

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

If this method returns {@code null}, the default display name + * generator will be used instead. + * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. Similarly, the class instance + * supplied as {@code testClass} may differ from the class returned by + * {@code testMethod.getDeclaringClass()} — for example, when a test + * method is inherited from a superclass. + * + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the test class, ordered from outermost to innermost, + * excluding {@code testClass}; never {@code null} + * @param testClass the class the test method is invoked on; never {@code null} + * @param testMethod method to generate a display name for; never {@code null} + * @return the display name for the test; never blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return generateDisplayNameForMethod(testClass, testMethod); + } /** * Generate a string representation of the formal parameters of the supplied @@ -141,12 +214,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return nestedClass.getSimpleName(); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return testMethod.getName() + parameterTypesAsString(testMethod); } } @@ -167,7 +241,8 @@ public Simple() { } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { String displayName = testMethod.getName(); if (hasParameters(testMethod)) { displayName += ' ' + parameterTypesAsString(testMethod); @@ -201,13 +276,15 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return replaceUnderscores(super.generateDisplayNameForNestedClass(nestedClass)); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return replaceUnderscores(super.generateDisplayNameForNestedClass(enclosingInstanceTypes, nestedClass)); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return replaceUnderscores(super.generateDisplayNameForMethod(testClass, testMethod)); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return replaceUnderscores( + super.generateDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod)); } private static String replaceUnderscores(String name) { @@ -238,22 +315,26 @@ public IndicativeSentences() { @Override public String generateDisplayNameForClass(Class testClass) { - return getGeneratorFor(testClass).generateDisplayNameForClass(testClass); + return getGeneratorFor(testClass, emptyList()).generateDisplayNameForClass(testClass); } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { - return getSentenceBeginning(nestedClass); + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { + return getSentenceBeginning(nestedClass, enclosingInstanceTypes); } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { - return getSentenceBeginning(testClass) + getFragmentSeparator(testClass) - + getGeneratorFor(testClass).generateDisplayNameForMethod(testClass, testMethod); + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return getSentenceBeginning(testClass, enclosingInstanceTypes) + + getFragmentSeparator(testClass, enclosingInstanceTypes) + + getGeneratorFor(testClass, enclosingInstanceTypes).generateDisplayNameForMethod( + enclosingInstanceTypes, testClass, testMethod); } - private String getSentenceBeginning(Class testClass) { - Class enclosingClass = testClass.getEnclosingClass(); + private String getSentenceBeginning(Class testClass, List> enclosingInstanceTypes) { + Class enclosingClass = enclosingInstanceTypes.isEmpty() ? null + : enclosingInstanceTypes.get(enclosingInstanceTypes.size() - 1); boolean topLevelTestClass = (enclosingClass == null || isStatic(testClass)); Optional displayName = findAnnotation(testClass, DisplayName.class)// .map(DisplayName::value).map(String::trim); @@ -262,27 +343,35 @@ private String getSentenceBeginning(Class testClass) { if (displayName.isPresent()) { return displayName.get(); } - Class generatorClass = findDisplayNameGeneration(testClass)// - .map(DisplayNameGeneration::value)// - .filter(not(IndicativeSentences.class))// - .orElse(null); + Class generatorClass = findDisplayNameGeneration(testClass, + enclosingInstanceTypes)// + .map(DisplayNameGeneration::value)// + .filter(not(IndicativeSentences.class))// + .orElse(null); if (generatorClass != null) { return getDisplayNameGenerator(generatorClass).generateDisplayNameForClass(testClass); } return generateDisplayNameForClass(testClass); } + List> remainingEnclosingInstanceTypes = enclosingInstanceTypes.isEmpty() ? emptyList() + : enclosingInstanceTypes.subList(0, enclosingInstanceTypes.size() - 1); + // Only build prefix based on the enclosing class if the enclosing // class is also configured to use the IndicativeSentences generator. - boolean buildPrefix = findDisplayNameGeneration(enclosingClass)// + boolean buildPrefix = findDisplayNameGeneration(enclosingClass, remainingEnclosingInstanceTypes)// .map(DisplayNameGeneration::value)// .filter(IndicativeSentences.class::equals)// .isPresent(); - String prefix = (buildPrefix ? getSentenceBeginning(enclosingClass) + getFragmentSeparator(testClass) : ""); + String prefix = (buildPrefix + ? getSentenceBeginning(enclosingClass, remainingEnclosingInstanceTypes) + + getFragmentSeparator(testClass, enclosingInstanceTypes) + : ""); return prefix + displayName.orElseGet( - () -> getGeneratorFor(testClass).generateDisplayNameForNestedClass(testClass)); + () -> getGeneratorFor(testClass, enclosingInstanceTypes).generateDisplayNameForNestedClass( + remainingEnclosingInstanceTypes, testClass)); } /** @@ -295,10 +384,12 @@ private String getSentenceBeginning(Class testClass) { * will be used. * * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances; never {@code null} * @return the sentence fragment separator */ - private static String getFragmentSeparator(Class testClass) { - return findIndicativeSentencesGeneration(testClass)// + private static String getFragmentSeparator(Class testClass, List> enclosingInstanceTypes) { + return findIndicativeSentencesGeneration(testClass, enclosingInstanceTypes)// .map(IndicativeSentencesGeneration::separator)// .orElse(IndicativeSentencesGeneration.DEFAULT_SEPARATOR); } @@ -313,10 +404,12 @@ private static String getFragmentSeparator(Class testClass) { * will be used. * * @param testClass the test class to search on for {@code @IndicativeSentencesGeneration} + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances; never {@code null} * @return the {@code DisplayNameGenerator} instance to use */ - private static DisplayNameGenerator getGeneratorFor(Class testClass) { - return findIndicativeSentencesGeneration(testClass)// + private static DisplayNameGenerator getGeneratorFor(Class testClass, List> enclosingInstanceTypes) { + return findIndicativeSentencesGeneration(testClass, enclosingInstanceTypes)// .map(IndicativeSentencesGeneration::generator)// .filter(not(IndicativeSentences.class))// .map(DisplayNameGenerator::getDisplayNameGenerator)// @@ -326,25 +419,32 @@ private static DisplayNameGenerator getGeneratorFor(Class testClass) { /** * Find the first {@code DisplayNameGeneration} annotation that is either * directly present, meta-present, or indirectly present - * on the supplied {@code testClass} or on an enclosing class. + * on the supplied {@code testClass} or on an enclosing instance type. * * @param testClass the test class on which to find the annotation; never {@code null} + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances; never {@code null} * @return an {@code Optional} containing the annotation, potentially empty if not found */ - private static Optional findDisplayNameGeneration(Class testClass) { - return findAnnotation(testClass, DisplayNameGeneration.class, true); + @API(status = INTERNAL, since = "5.12") + private static Optional findDisplayNameGeneration(Class testClass, + List> enclosingInstanceTypes) { + return findAnnotation(testClass, DisplayNameGeneration.class, enclosingInstanceTypes); } /** * Find the first {@code IndicativeSentencesGeneration} annotation that is either * directly present, meta-present, or indirectly present - * on the supplied {@code testClass} or on an enclosing class. + * on the supplied {@code testClass} or on an enclosing instance type. * * @param testClass the test class on which to find the annotation; never {@code null} + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances; never {@code null} * @return an {@code Optional} containing the annotation, potentially empty if not found */ - private static Optional findIndicativeSentencesGeneration(Class testClass) { - return findAnnotation(testClass, IndicativeSentencesGeneration.class, true); + private static Optional findIndicativeSentencesGeneration(Class testClass, + List> enclosingInstanceTypes) { + return findAnnotation(testClass, IndicativeSentencesGeneration.class, enclosingInstanceTypes); } private static Predicate> not(Class clazz) { @@ -377,7 +477,7 @@ static DisplayNameGenerator getDisplayNameGenerator(Class generatorClass) { if (generatorClass == IndicativeSentences.class) { return IndicativeSentences.INSTANCE; } - return (DisplayNameGenerator) ReflectionUtils.newInstance(generatorClass); + return (DisplayNameGenerator) ReflectionSupport.newInstance(generatorClass); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java index 5a37b8e156fd..da947b68c778 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java index be569469d852..43574ee853a7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java index b61ccb71a57a..d87ae1ab4f71 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java index 9a24a7e4e1d6..722e91bf4d09 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/IndicativeSentencesGeneration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java index 7fbf3f569b32..5026d5a448c7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java index 4a71d5944551..e853106480f6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -88,8 +88,7 @@ public interface MethodOrderer { *

 	 * public void orderMethods(MethodOrdererContext context) {
 	 *     Collections.shuffle(context.getMethodDescriptors());
-	 * }
-	 * 
+ * }
* * @param context the {@code MethodOrdererContext} containing the * {@linkplain MethodDescriptor method descriptors} to order; never {@code null} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java index bab5d3e10ec5..38f1d90b2467 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/MethodOrdererContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java index e41b4be29ab3..dfeff26354e4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/NamedExecutable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/NamedExecutable.java index 16d5a72d2d18..c11a631885f3 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/NamedExecutable.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/NamedExecutable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java index 2e9c559e9390..4d96618238d0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Nested.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java index 3055cdc2cd0d..7ea0fbadbca9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Order.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java index 35b5dc6208ae..de221d3d5d61 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RandomOrdererUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java index 063228c97486..15c991357c4e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepeatedTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java index e7b84a024ac1..a24dce3e3d6c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/RepetitionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java index 87777564aaaf..ffc31b21ffcc 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tag.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java index 5b681705a018..840adf79312e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Tags.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java index f17d735904a4..052508f90a62 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Test.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java index 86bc8c66a9da..0f9ad73ddd30 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestClassOrder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -57,8 +57,7 @@ * class SecondaryTests { * // {@literal @}Test methods ... * } - * } - *
+ * } * * @since 5.8 * @see ClassOrderer 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 dd17d2647186..42835ebd6888 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java index 518a4ad69c15..79d235d4ada4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -68,8 +68,7 @@ public interface TestInfo { * class NestedTests {} * * static class StaticTests {} - * } - * + * } * *

If the context in which {@code TestInfo} is used is at the test level, * the default display name is the name of the test method concatenated with @@ -79,8 +78,7 @@ public interface TestInfo { * *

 	 *   {@literal @}Test
-	 *   void testUser(TestInfo testInfo, {@literal @}Mock User user) {}
-	 * 
+ * void testUser(TestInfo testInfo, {@literal @}Mock User user) {} * *

Note that display names are typically used for test reporting in IDEs * and build tools and may contain spaces, special characters, and even emoji. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java index 8ee6292cd5f8..80f904a41987 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestInstance.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; +import org.junit.jupiter.api.parallel.Execution; /** * {@code @TestInstance} is a type-level annotation that is used to configure @@ -59,8 +60,14 @@ * create a custom composed annotation that inherits the semantics * of {@code @TestInstance}. * + *

Parallel Execution

+ *

Using the {@link Lifecycle#PER_CLASS PER_CLASS} lifecycle mode disables + * parallel execution unless the test class or test method is annotated with + * {@link Execution @Execution(CONCURRENT)}. + * * @since 5.0 * @see Nested @Nested + * @see Execution @Execution */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java index 44ddd8cf31fe..c1df77510c45 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestMethodOrder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; +import org.junit.jupiter.api.parallel.Execution; /** * {@code @TestMethodOrder} is a type-level annotation that is used to configure @@ -61,8 +62,12 @@ * {@literal @}Test * {@literal @}Order(3) * void validValues() {} - * } - * + * } + * + *

Parallel Execution

+ *

Using a {@link MethodOrderer} disables parallel execution unless the test + * class or test method is annotated with + * {@link Execution @Execution(CONCURRENT)}. * * @since 5.4 * @see MethodOrderer diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java index ffcf580603ff..85e28acc3bce 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,12 +10,22 @@ package org.junit.jupiter.api; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.Map; +import java.util.stream.Stream; import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.commons.util.Preconditions; /** * Parameters of type {@code TestReporter} can be injected into @@ -77,4 +87,93 @@ default void publishEntry(String value) { this.publishEntry("value", value); } + /** + * Publish the supplied file and attach it to the current test or container. + *

+ * The file will be copied to the report output directory replacing any + * potentially existing file with the same name. + * + * @param file the file to be attached; never {@code null} or blank + * @param mediaType the media type of the file; never {@code null}; use + * {@link MediaType#APPLICATION_OCTET_STREAM} if unknown + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default void publishFile(Path file, MediaType mediaType) { + Preconditions.condition(Files.exists(file), () -> "file must exist: " + file); + Preconditions.condition(Files.isRegularFile(file), () -> "file must be a regular file: " + file); + publishFile(file.getFileName().toString(), mediaType, path -> Files.copy(file, path, REPLACE_EXISTING)); + } + + /** + * Publish the supplied directory and attach it to the current test or + * container. + *

+ * The entire directory will be copied to the report output directory + * replacing any potentially existing files with the same name. + * + * @param directory the file to be attached; never {@code null} or blank + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default void publishDirectory(Path directory) { + Preconditions.condition(Files.exists(directory), () -> "directory must exist: " + directory); + Preconditions.condition(Files.isDirectory(directory), () -> "directory must be a directory: " + directory); + publishDirectory(directory.getFileName().toString(), path -> { + try (Stream stream = Files.walk(directory)) { + stream.forEach(source -> { + Path destination = path.resolve(directory.relativize(source)); + try { + if (Files.isDirectory(source)) { + Files.createDirectories(destination); + } + else { + Files.copy(source, destination, REPLACE_EXISTING); + } + } + catch (IOException e) { + throw new UncheckedIOException("Failed to copy files to the output directory", e); + } + }); + } + }); + } + + /** + * Publish a file or directory with the supplied name and media type written + * by the supplied action and attach it to the current test or container. + *

+ * The {@link Path} passed to the supplied action will be relative to the + * report output directory, but it's up to the action to write the file. + * + * @param name the name of the file to be attached; never {@code null} or + * blank and must not contain any path separators + * @param mediaType the media type of the file; never {@code null}; use + * {@link MediaType#APPLICATION_OCTET_STREAM} if unknown + * @param action the action to be executed to write the file; never {@code null} + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default void publishFile(String name, MediaType mediaType, ThrowingConsumer action) { + throw new UnsupportedOperationException(); + } + + /** + * Publish a directory with the supplied name written by the supplied action + * and attach it to the current test or container. + *

+ * The {@link Path} passed to the supplied action will be relative to the + * report output directory and point to an existing directory, but it's up + * to the action to write files to it. + * + * @param name the name of the directory to be attached; never {@code null} + * or blank and must not contain any path separators + * @param action the action to be executed to write the file; never {@code null} + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default void publishDirectory(String name, ThrowingConsumer action) { + throw new UnsupportedOperationException(); + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java index c9d337108405..9636a2d01679 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java index 831fb2963686..f7081d629cda 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Timeout.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -358,8 +358,8 @@ ThreadMode threadMode() default ThreadMode.INFERRED; /** - * {@code ThreadMode} is use to define whether the test code should be executed in the thread - * of the calling code or in a separated thread. + * {@code ThreadMode} is used to define whether test code should be executed + * in the thread of the calling code or in a separate thread. * * @since 5.9 */ 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 new file mode 100644 index 000000000000..43ca0b92555d --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreCondition.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.function.Function; +import java.util.stream.IntStream; + +import org.junit.platform.commons.util.Preconditions; + +/** + * Abstract base class for {@link EnabledOnJreCondition} and + * {@link DisabledOnJreCondition}. + * + * @since 5.12 + */ +abstract class AbstractJreCondition extends BooleanExecutionCondition { + + static final String ENABLED_ON_CURRENT_JRE = // + "Enabled on JRE version: " + System.getProperty("java.version"); + + static final String DISABLED_ON_CURRENT_JRE = // + "Disabled on JRE version: " + System.getProperty("java.version"); + + private final String annotationName; + + AbstractJreCondition(Class annotationType, Function customDisabledReason) { + super(annotationType, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, customDisabledReason); + this.annotationName = annotationType.getSimpleName(); + } + + 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); + + 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; + })// + ).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 new file mode 100644 index 000000000000..ea2290e5c8de --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractJreRangeCondition.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.condition.AbstractJreCondition.DISABLED_ON_CURRENT_JRE; +import static org.junit.jupiter.api.condition.AbstractJreCondition.ENABLED_ON_CURRENT_JRE; + +import java.lang.annotation.Annotation; +import java.util.function.Function; + +import org.junit.platform.commons.util.Preconditions; + +/** + * Abstract base class for {@link EnabledForJreRangeCondition} and + * {@link DisabledForJreRangeCondition}. + * + * @since 5.12 + */ +abstract class AbstractJreRangeCondition extends BooleanExecutionCondition { + + private final String annotationName; + + AbstractJreRangeCondition(Class annotationType, Function customDisabledReason) { + super(annotationType, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, customDisabledReason); + this.annotationName = annotationType.getSimpleName(); + } + + protected final boolean isCurrentVersionWithinRange(JRE minJre, JRE maxJre, int minVersion, int maxVersion) { + boolean minJreSet = minJre != JRE.UNDEFINED; + boolean maxJreSet = maxJre != JRE.UNDEFINED; + boolean minVersionSet = minVersion != JRE.UNDEFINED_VERSION; + boolean maxVersionSet = maxVersion != JRE.UNDEFINED_VERSION; + + // Users must choose between JRE enum constants and version numbers. + Preconditions.condition(!minJreSet || !minVersionSet, () -> String.format( + "@%s's minimum value must be configured with either a JRE enum constant or numeric version, but not both", + this.annotationName)); + Preconditions.condition(!maxJreSet || !maxVersionSet, () -> String.format( + "@%s's maximum value must be configured with either a JRE enum constant or numeric version, but not both", + this.annotationName)); + + // Users must supply valid values for minVersion and maxVersion. + Preconditions.condition(!minVersionSet || (minVersion >= JRE.MINIMUM_VERSION), + () -> String.format("@%s's minVersion [%d] must be greater than or equal to %d", this.annotationName, + minVersion, JRE.MINIMUM_VERSION)); + Preconditions.condition(!maxVersionSet || (maxVersion >= JRE.MINIMUM_VERSION), + () -> String.format("@%s's maxVersion [%d] must be greater than or equal to %d", this.annotationName, + maxVersion, JRE.MINIMUM_VERSION)); + + // Now that we have checked the basic preconditions, we need to ensure that we are + // using valid JRE enum constants. + if (!minJreSet) { + minJre = JRE.JAVA_8; + } + if (!maxJreSet) { + maxJre = JRE.OTHER; + } + + int min = (minVersionSet ? minVersion : minJre.version()); + int max = (maxVersionSet ? maxVersion : maxJre.version()); + + // 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)); + + return JRE.isCurrentVersionWithinRange(min, max); + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java index b27d52c7332d..c4ae691aee63 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractOsBasedExecutionCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.jupiter.api.condition; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.Annotation; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java index 2e29da7e653b..66cc3d2e61c6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/AbstractRepeatableAnnotationCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.jupiter.api.condition; import static java.lang.String.format; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java index 3d84af41b40b..0bc4281b9618 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/BooleanExecutionCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,7 +12,7 @@ import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.Annotation; import java.util.function.Function; @@ -23,31 +23,32 @@ abstract class BooleanExecutionCondition implements ExecutionCondition { - private final Class annotationType; + protected final Class annotationType; private final String enabledReason; private final String disabledReason; private final Function customDisabledReason; BooleanExecutionCondition(Class annotationType, String enabledReason, String disabledReason, Function customDisabledReason) { + this.annotationType = annotationType; this.enabledReason = enabledReason; this.disabledReason = disabledReason; this.customDisabledReason = customDisabledReason; } - abstract boolean isEnabled(A annotation); - @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - return findAnnotation(context.getElement(), annotationType) // - .map(annotation -> isEnabled(annotation) ? enabled(enabledReason) - : disabled(disabledReason, customDisabledReason.apply(annotation))) // + return findAnnotation(context.getElement(), this.annotationType) // + .map(annotation -> isEnabled(annotation) ? enabled(this.enabledReason) + : disabled(this.disabledReason, this.customDisabledReason.apply(annotation))) // .orElseGet(this::enabledByDefault); } + abstract boolean isEnabled(A annotation); + private ConditionEvaluationResult enabledByDefault() { - String reason = String.format("@%s is not present", annotationType.getSimpleName()); + String reason = String.format("@%s is not present", this.annotationType.getSimpleName()); return enabled(reason); } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java index 2ca116acfb9f..f2b077a0b2f3 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.api.condition; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -24,7 +25,11 @@ /** * {@code @DisabledForJreRange} is used to signal that the annotated test class * or test method is disabled for a specific range of Java Runtime - * Environment (JRE) versions from {@link #min} to {@link #max}. + * Environment (JRE) versions. + * + *

Version ranges can be specified as {@link JRE} enum constants via + * {@link #min min} and {@link #max max} or as integers via + * {@link #minVersion minVersion} and {@link #maxVersion maxVersion}. * *

When applied at the class level, all test methods within that class will * be disabled on the same specified JRE versions. @@ -82,28 +87,78 @@ public @interface DisabledForJreRange { /** - * Java Runtime Environment version which is used as the lower boundary - * for the version range that determines if the annotated class or method - * should be disabled. + * Java Runtime Environment version which is used as the lower boundary for + * the version range that determines if the annotated class or method should + * be disabled, specified as a {@link JRE} enum constant. + * + *

If a {@code JRE} enum constant does not exist for a particular JRE + * version, you can specify the minimum version via + * {@link #minVersion() minVersion} instead. * - *

Defaults to {@link JRE#JAVA_8 JAVA_8}, as this is the lowest - * supported JRE version. + *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted + * as {@link JRE#JAVA_8 JAVA_8} if the {@link #minVersion() minVersion} is + * not set. * * @see JRE + * @see #minVersion() */ - JRE min() default JRE.JAVA_8; + JRE min() default JRE.UNDEFINED; /** - * Java Runtime Environment version which is used as the upper boundary - * for the version range that determines if the annotated class or method - * should be disabled. + * Java Runtime Environment version which is used as the upper boundary for + * the version range that determines if the annotated class or method should + * be disabled, specified as a {@link JRE} enum constant. * - *

Defaults to {@link JRE#OTHER OTHER}, as this will always be the highest - * possible version. + *

If a {@code JRE} enum constant does not exist for a particular JRE + * version, you can specify the maximum version via + * {@link #maxVersion() maxVersion} instead. + * + *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted + * as {@link JRE#OTHER OTHER} if the {@link #maxVersion() maxVersion} is not + * set. * * @see JRE + * @see #maxVersion() + */ + JRE max() default JRE.UNDEFINED; + + /** + * Java Runtime Environment version which is used as the lower boundary for + * the version range that determines if the annotated class or method should + * be disabled, specified as an integer. + * + *

If a {@code JRE} enum constant exists for the particular JRE version, + * you can specify the minimum version via {@link #min() min} instead. + * + *

Defaults to {@code -1} to signal that {@link #min() min} should be used + * instead. + * + * @since 5.12 + * @see #min() + * @see JRE#version() + * @see Runtime.Version#feature() + */ + @API(status = EXPERIMENTAL, since = "5.12") + int minVersion() default -1; + + /** + * Java Runtime Environment version which is used as the upper boundary for + * the version range that determines if the annotated class or method should + * be disabled, specified as an integer. + * + *

If a {@code JRE} enum constant exists for the particular JRE version, + * you can specify the maximum version via {@link #max() max} instead. + * + *

Defaults to {@code -1} to signal that {@link #max() max} should be used + * instead. + * + * @since 5.12 + * @see #max() + * @see JRE#version() + * @see Runtime.Version#feature() */ - JRE max() default JRE.OTHER; + @API(status = EXPERIMENTAL, since = "5.12") + int maxVersion() default -1; /** * Custom reason to provide if the test or container is disabled. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java index fb8d45b0963e..7f753cccf8e5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledForJreRangeCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,11 +10,7 @@ package org.junit.jupiter.api.condition; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; - import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link DisabledForJreRange @DisabledForJreRange}. @@ -22,24 +18,15 @@ * @since 5.6 * @see DisabledForJreRange */ -class DisabledForJreRangeCondition extends BooleanExecutionCondition { +class DisabledForJreRangeCondition extends AbstractJreRangeCondition { DisabledForJreRangeCondition() { - super(DisabledForJreRange.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, - DisabledForJreRange::disabledReason); + super(DisabledForJreRange.class, DisabledForJreRange::disabledReason); } @Override - boolean isEnabled(DisabledForJreRange annotation) { - JRE min = annotation.min(); - JRE max = annotation.max(); - - Preconditions.condition((min != JRE.JAVA_8 || max != JRE.OTHER), - "You must declare a non-default value for min or max in @DisabledForJreRange"); - Preconditions.condition(max.compareTo(min) >= 0, - "@DisabledForJreRange.min must be less than or equal to @DisabledForJreRange.max"); - - return !JRE.isCurrentVersionWithinRange(min, max); + boolean isEnabled(DisabledForJreRange range) { + return !isCurrentVersionWithinRange(range.min(), range.max(), range.minVersion(), range.maxVersion()); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java index 6e823856cc38..070279b9a461 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIf.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java index 2d27cbeb8f8a..26ebe9bd3df1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java index 69c600576056..fb1a35ad1e61 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java index 43dd839d544c..cac8fe5300c4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java index b29e874949cf..8bedc3fd37ef 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariables.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java index f103e3066b29..3e22c0f20ebd 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java index 43e856b6a9c6..5cb96077a9cb 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java index d28a906c0150..45abb83f8cea 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java index e7505d328a00..330ddb6a8d9b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledInNativeImage.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java index ed1c6e272511..28281d086430 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJre.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.api.condition; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -24,7 +25,10 @@ /** * {@code @DisabledOnJre} is used to signal that the annotated test class or * test method is disabled on one or more specified Java Runtime - * Environment (JRE) {@linkplain #value versions}. + * Environment (JRE) versions. + * + *

Versions can be specified as {@link JRE} enum constants via + * {@link #value() value} or as integers via {@link #versions() versions}. * *

When applied at the class level, all test methods within that class * will be disabled on the same specified JRE versions. @@ -82,12 +86,32 @@ public @interface DisabledOnJre { /** - * Java Runtime Environment versions on which the annotated class or - * method should be disabled. + * Java Runtime Environment versions on which the annotated class or method + * should be disabled, specified as {@link JRE} enum constants. + * + *

If a {@code JRE} enum constant does not exist for a particular JRE + * version, you can specify the version via {@link #versions() versions} + * instead. * * @see JRE + * @see #versions() + */ + JRE[] value() default {}; + + /** + * Java Runtime Environment versions on which the annotated class or method + * should be disabled, specified as integers. + * + *

If a {@code JRE} enum constant exists for a particular JRE version, you + * can specify the version via {@link #value() value} instead. + * + * @since 5.12 + * @see #value() + * @see JRE#version() + * @see Runtime.Version#feature() */ - JRE[] value(); + @API(status = EXPERIMENTAL, since = "5.12") + int[] versions() default {}; /** * Custom reason to provide if the test or container is disabled. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java index fd2b0996c5e2..149f3744f478 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnJreCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,13 +10,7 @@ package org.junit.jupiter.api.condition; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; - -import java.util.Arrays; - import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link DisabledOnJre @DisabledOnJre}. @@ -24,17 +18,15 @@ * @since 5.1 * @see DisabledOnJre */ -class DisabledOnJreCondition extends BooleanExecutionCondition { +class DisabledOnJreCondition extends AbstractJreCondition { DisabledOnJreCondition() { - super(DisabledOnJre.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, DisabledOnJre::disabledReason); + super(DisabledOnJre.class, DisabledOnJre::disabledReason); } @Override boolean isEnabled(DisabledOnJre annotation) { - JRE[] versions = annotation.value(); - Preconditions.condition(versions.length > 0, "You must declare at least one JRE in @DisabledOnJre"); - return Arrays.stream(versions).noneMatch(JRE::isCurrentVersion); + return validatedVersions(annotation.value(), annotation.versions()).noneMatch(JRE::isCurrentVersion); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java index a8dd1dd0fe3e..53356e601f19 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOs.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java index 714c74cb37bd..27328a81568a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/DisabledOnOsCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java index 1ccd390c08e8..d60bae33d75d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRange.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.api.condition; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -24,7 +25,11 @@ /** * {@code @EnabledForJreRange} is used to signal that the annotated test class or * test method is only enabled for a specific range of Java Runtime - * Environment (JRE) versions from {@link #min} to {@link #max}. + * Environment (JRE) versions. + * + *

Version ranges can be specified as {@link JRE} enum constants via + * {@link #min min} and {@link #max max} or as integers via + * {@link #minVersion minVersion} and {@link #maxVersion maxVersion}. * *

When applied at the class level, all test methods within that class will * be enabled on the same specified JRE versions. @@ -82,28 +87,78 @@ public @interface EnabledForJreRange { /** - * Java Runtime Environment version which should be used as the lower boundary - * for the version range that determines if the annotated class or method - * should be enabled. + * Java Runtime Environment version which is used as the lower boundary for + * the version range that determines if the annotated class or method should + * be enabled, specified as a {@link JRE} enum constant. + * + *

If a {@code JRE} enum constant does not exist for a particular JRE + * version, you can specify the minimum version via + * {@link #minVersion() minVersion} instead. * - *

Defaults to {@link JRE#JAVA_8 JAVA_8}, as this is the lowest - * supported JRE version. + *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted + * as {@link JRE#JAVA_8 JAVA_8} if the {@link #minVersion() minVersion} is + * not set. * * @see JRE + * @see #minVersion() */ - JRE min() default JRE.JAVA_8; + JRE min() default JRE.UNDEFINED; /** - * Java Runtime Environment version which should be used as the upper boundary - * for the version range that determines if the annotated class or method - * should be enabled. + * Java Runtime Environment version which is used as the upper boundary for + * the version range that determines if the annotated class or method should + * be enabled, specified as a {@link JRE} enum constant. * - *

Defaults to {@link JRE#OTHER OTHER}, as this will always be the highest - * possible version. + *

If a {@code JRE} enum constant does not exist for a particular JRE + * version, you can specify the maximum version via + * {@link #maxVersion() maxVersion} instead. + * + *

Defaults to {@link JRE#UNDEFINED UNDEFINED}, which will be interpreted + * as {@link JRE#OTHER OTHER} if the {@link #maxVersion() maxVersion} is not + * set. * * @see JRE + * @see #maxVersion() + */ + JRE max() default JRE.UNDEFINED; + + /** + * Java Runtime Environment version which is used as the lower boundary for + * the version range that determines if the annotated class or method should + * be enabled, specified as an integer. + * + *

If a {@code JRE} enum constant exists for the particular JRE version, + * you can specify the minimum version via {@link #min() min} instead. + * + *

Defaults to {@code -1} to signal that {@link #min() min} should be used + * instead. + * + * @since 5.12 + * @see #min() + * @see JRE#version() + * @see Runtime.Version#feature() + */ + @API(status = EXPERIMENTAL, since = "5.12") + int minVersion() default -1; + + /** + * Java Runtime Environment version which is used as the upper boundary for + * the version range that determines if the annotated class or method should + * be enabled, specified as an integer. + * + *

If a {@code JRE} enum constant exists for the particular JRE version, + * you can specify the maximum version via {@link #max() max} instead. + * + *

Defaults to {@code -1} to signal that {@link #max() max} should be used + * instead. + * + * @since 5.12 + * @see #max() + * @see JRE#version() + * @see Runtime.Version#feature() */ - JRE max() default JRE.OTHER; + @API(status = EXPERIMENTAL, since = "5.12") + int maxVersion() default -1; /** * Custom reason to provide if the test or container is disabled. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java index 0c7c0187778e..bea585b13dc1 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledForJreRangeCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,11 +10,7 @@ package org.junit.jupiter.api.condition; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.DISABLED_ON_CURRENT_JRE; -import static org.junit.jupiter.api.condition.EnabledOnJreCondition.ENABLED_ON_CURRENT_JRE; - import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link EnabledForJreRange @EnabledForJreRange}. @@ -22,24 +18,15 @@ * @since 5.6 * @see EnabledForJreRange */ -class EnabledForJreRangeCondition extends BooleanExecutionCondition { +class EnabledForJreRangeCondition extends AbstractJreRangeCondition { EnabledForJreRangeCondition() { - super(EnabledForJreRange.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, - EnabledForJreRange::disabledReason); + super(EnabledForJreRange.class, EnabledForJreRange::disabledReason); } @Override - boolean isEnabled(EnabledForJreRange annotation) { - JRE min = annotation.min(); - JRE max = annotation.max(); - - Preconditions.condition((min != JRE.JAVA_8 || max != JRE.OTHER), - "You must declare a non-default value for min or max in @EnabledForJreRange"); - Preconditions.condition(max.compareTo(min) >= 0, - "@EnabledForJreRange.min must be less than or equal to @EnabledForJreRange.max"); - - return JRE.isCurrentVersionWithinRange(min, max); + boolean isEnabled(EnabledForJreRange range) { + return isCurrentVersionWithinRange(range.min(), range.max(), range.minVersion(), range.maxVersion()); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java index 56d72c8fce7d..79837a15e7f6 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIf.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java index 2edcec77b7b9..fbbb228f4ae8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java index 85d455d98582..941b823ec44c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java index b336e6d78a64..050206f5559f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java index 60d8b5de9cfb..32102bd4a95b 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariables.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java index 1f26ceef55a5..640e6a6ea97c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java index a2f8fb004d60..226211961470 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemProperty.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java index f6d5ffb79ebf..00ce9a4c4c1f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java index c2c7993adafe..daafe41ad897 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledInNativeImage.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java index b37776e31ee6..5671e8c2bc51 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJre.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.api.condition; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -23,8 +24,11 @@ /** * {@code @EnabledOnJre} is used to signal that the annotated test class or - * test method is only enabled on one or more specified Java - * Runtime Environment (JRE) {@linkplain #value versions}. + * test method is only enabled on one or more specified Java Runtime + * Environment (JRE) versions. + * + *

Versions can be specified as {@link JRE} enum constants via + * {@link #value() value} or as integers via {@link #versions() versions}. * *

When applied at the class level, all test methods within that class * will be enabled on the same specified JRE versions. @@ -82,12 +86,32 @@ public @interface EnabledOnJre { /** - * Java Runtime Environment versions on which the annotated class or - * method should be enabled. + * Java Runtime Environment versions on which the annotated class or method + * should be enabled, specified as {@link JRE} enum constants. + * + *

If a {@code JRE} enum constant does not exist for a particular JRE + * version, you can specify the version via {@link #versions() versions} + * instead. * * @see JRE + * @see #versions() + */ + JRE[] value() default {}; + + /** + * Java Runtime Environment versions on which the annotated class or method + * should be enabled, specified as integers. + * + *

If a {@code JRE} enum constant exists for a particular JRE version, you + * can specify the version via {@link #value() value} instead. + * + * @since 5.12 + * @see #value() + * @see JRE#version() + * @see Runtime.Version#feature() */ - JRE[] value(); + @API(status = EXPERIMENTAL, since = "5.12") + int[] versions() default {}; /** * Custom reason to provide if the test or container is disabled. @@ -100,4 +124,5 @@ */ @API(status = STABLE, since = "5.7") String disabledReason() default ""; + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java index 70c0b3867a66..2152e73b1a8e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnJreCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,7 @@ package org.junit.jupiter.api.condition; -import java.util.Arrays; - import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.util.Preconditions; /** * {@link ExecutionCondition} for {@link EnabledOnJre @EnabledOnJre}. @@ -21,23 +18,15 @@ * @since 5.1 * @see EnabledOnJre */ -class EnabledOnJreCondition extends BooleanExecutionCondition { - - static final String ENABLED_ON_CURRENT_JRE = // - "Enabled on JRE version: " + System.getProperty("java.version"); - - static final String DISABLED_ON_CURRENT_JRE = // - "Disabled on JRE version: " + System.getProperty("java.version"); +class EnabledOnJreCondition extends AbstractJreCondition { EnabledOnJreCondition() { - super(EnabledOnJre.class, ENABLED_ON_CURRENT_JRE, DISABLED_ON_CURRENT_JRE, EnabledOnJre::disabledReason); + super(EnabledOnJre.class, EnabledOnJre::disabledReason); } @Override boolean isEnabled(EnabledOnJre annotation) { - JRE[] versions = annotation.value(); - Preconditions.condition(versions.length > 0, "You must declare at least one JRE in @EnabledOnJre"); - return Arrays.stream(versions).anyMatch(JRE::isCurrentVersion); + return validatedVersions(annotation.value(), annotation.versions()).anyMatch(JRE::isCurrentVersion); } } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java index eff74e6ccb57..3fdece340e1a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOs.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java index 1853b9f09f8a..cf9e9d6682f9 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/EnabledOnOsCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java index f06738d1eea2..48088225cf01 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/MethodBasedCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,7 @@ import static java.lang.String.format; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -25,6 +25,7 @@ import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; @@ -67,13 +68,13 @@ Method getConditionMethod(String fullyQualifiedMethodName, ExtensionContext cont String className = methodParts[0]; String methodName = methodParts[1]; ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass); - Class clazz = ReflectionUtils.tryToLoadClass(className, classLoader).getOrThrow( + Class clazz = ReflectionSupport.tryToLoadClass(className, classLoader).getOrThrow( cause -> new JUnitException(format("Could not load class [%s]", className), cause)); return findMethod(clazz, methodName); } private Method findMethod(Class clazz, String methodName) { - return ReflectionUtils.findMethod(clazz, methodName) // + return ReflectionSupport.findMethod(clazz, methodName) // .orElseGet(() -> ReflectionUtils.getRequiredMethod(clazz, methodName, ExtensionContext.class)); } @@ -85,9 +86,9 @@ private boolean invokeConditionMethod(Method method, ExtensionContext context) { Object testInstance = context.getTestInstance().orElse(null); if (method.getParameterCount() == 0) { - return (boolean) ReflectionUtils.invokeMethod(method, testInstance); + return (boolean) ReflectionSupport.invokeMethod(method, testInstance); } - return (boolean) ReflectionUtils.invokeMethod(method, testInstance, context); + return (boolean) ReflectionSupport.invokeMethod(method, testInstance, context); } private boolean acceptsExtensionContextOrNoArguments(Method method) { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java index 9e4094599fa2..49878580bd66 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/condition/OS.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java index b98600d85849..4c50c5f46c12 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterAllCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java index 2d8724e1974c..a1be4e60e374 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterEachCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java index 72ca8e31d8a1..367985a93914 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AfterTestExecutionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java index 924e4a918d93..19ad328fb7a4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/AnnotatedElementContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -55,6 +55,10 @@ public interface AnnotatedElementContext { * present or meta-present on the {@link AnnotatedElement} for * this context. * + *

Note: This method does not find repeatable annotations. + * To check for repeatable annotations, use {@link #findRepeatableAnnotations(Class)} + * and verify that the returned list is not empty. + * *

WARNING

*

Favor the use of this method over directly invoking * {@link AnnotatedElement#isAnnotationPresent(Class)} due to a bug in {@code javac} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java index 5533e1169904..d546e0da035f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeAllCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java index 273a7de1f900..6b23ad51df73 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeEachCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java index 35b634c47473..56094d6a133f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java index 9b67863e51bb..316d5961813c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ConditionEvaluationResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java index f92391570724..e835648b3cb8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/DynamicTestInvocationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java index 4e1b8e753d31..37645f08e1ee 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutableInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java index d82687f4fd22..31af5c81a917 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExecutionCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java index 036e7eb90396..7bbab30aa8d3 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtendWith.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java index 78c68ab96867..f5cee6257919 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java index 49e6defb985d..91e5c7ee997d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionConfigurationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 44a3447a4e7b..a2bfd4db7d80 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,12 @@ package org.junit.jupiter.api.extension; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -25,6 +27,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.support.ReflectionSupport; @@ -364,6 +367,40 @@ default void publishReportEntry(String value) { this.publishReportEntry("value", value); } + /** + * Publish a file with the supplied name written by the supplied action and + * attach it to the current test or container. + *

+ * The file will be resolved in the report output directory prior to + * invoking the supplied action. + * + * @param name the name of the file to be attached; never {@code null} or + * blank and must not contain any path separators + * @param mediaType the media type of the file; never {@code null}; use + * {@link MediaType#APPLICATION_OCTET_STREAM} if unknown + * @param action the action to be executed to write the file; never {@code null} + * @since 5.12 + * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished + */ + @API(status = EXPERIMENTAL, since = "5.12") + void publishFile(String name, MediaType mediaType, ThrowingConsumer action); + + /** + * Publish a directory with the supplied name written by the supplied action + * and attach it to the current test or container. + *

+ * The directory will be resolved and created in the report output directory + * prior to invoking the supplied action. + * + * @param name the name of the directory to be attached; never {@code null} + * or blank and must not contain any path separators + * @param action the action to be executed to write the file; never {@code null} + * @since 5.12 + * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished + */ + @API(status = EXPERIMENTAL, since = "5.12") + void publishDirectory(String name, ThrowingConsumer action); + /** * Get the {@link Store} for the supplied {@link Namespace}. * diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java index 56192607892c..c31797d17362 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContextException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java index 652c606d63cb..a9817b4866ba 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/Extensions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -33,7 +33,7 @@ * @see ExtendWith * @see java.lang.annotation.Repeatable */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java index 81bf9dc1fd32..dc02002508d0 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/InvocationInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -24,6 +24,7 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestTemplate; /** @@ -50,7 +51,7 @@ * @see ExtensionContext */ @API(status = STABLE, since = "5.10") -public interface InvocationInterceptor extends Extension { +public interface InvocationInterceptor extends TestInstantiationAwareExtension { /** * Intercept the invocation of a test class constructor. @@ -58,6 +59,16 @@ public interface InvocationInterceptor extends Extension { *

Note that the test class may not have been initialized * (static initialization) when this method is invoked. * + *

By default, the supplied {@link ExtensionContext} represents the test + * class that's about to be constructed. Extensions may override + * {@link #getTestInstantiationExtensionContextScope} to return + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change + * the scope of the {@code ExtensionContext} to the test method, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. + * Changing the scope makes test-specific data available to the + * implementation of this method and allows keeping state on the test level + * by using the provided {@link ExtensionContext.Store Store} instance. + * * @param invocation the invocation that is being intercepted; never * {@code null} * @param invocationContext the context of the invocation that is being diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java index de752e28eb1b..c4078c9ef45f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/LifecycleMethodExecutionExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java new file mode 100644 index 000000000000..00ad02dadfd2 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/MediaType.java @@ -0,0 +1,155 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; + +/** + * Represents a media type as defined by + * RFC 2045. + * + * @since 5.12 + * @see TestReporter#publishFile(Path, MediaType) + * @see TestReporter#publishFile(String, MediaType, ThrowingConsumer) + * @see ExtensionContext#publishFile(String, MediaType, ThrowingConsumer) + */ +@API(status = EXPERIMENTAL, since = "5.12") +public class MediaType { + + private static final Pattern PATTERN; + static { + // https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 + String whitespace = "[ \t]*"; + String token = "[0-9A-Za-z!#$%&'*+.^_`|~-]+"; + String quotedString = "\"(?:[^\"\\\\]|\\.)*\""; + String parameter = ";" + whitespace + token + "=" + "(?:" + token + "|" + quotedString + ")"; + PATTERN = Pattern.compile(token + "/" + token + "(?:" + whitespace + parameter + ")*"); + } + + /** + * The {@code text/plain} media type. + */ + public static final MediaType TEXT_PLAIN = create("text", "plain"); + + /** + * The {@code text/plain; charset=UTF-8} media type. + */ + public static final MediaType TEXT_PLAIN_UTF_8 = create("text", "plain", UTF_8); + + /** + * The {@code application/json} media type. + */ + public static final MediaType APPLICATION_JSON = create("application", "json"); + + /** + * The {@code application/json; charset=UTF-8} media type. + */ + public static final MediaType APPLICATION_JSON_UTF_8 = create("application", "json", UTF_8); + + /** + * The {@code application/octet-stream} media type. + */ + public static final MediaType APPLICATION_OCTET_STREAM = create("application", "octet-stream"); + + /** + * The {@code image/jpeg} media type. + */ + public static final MediaType IMAGE_JPEG = create("image", "jpeg"); + + /** + * The {@code image/png} media type. + */ + public static final MediaType IMAGE_PNG = create("image", "png"); + + private final String value; + + /** + * Parse the given media type value. + *

+ * Must be valid according to + * RFC 2045. + * + * @param value the media type value to parse; never {@code null} + * @return the parsed media type + * @throws PreconditionViolationException if the value is not a valid media type + */ + public static MediaType parse(String value) { + return new MediaType(value); + } + + /** + * Create a media type with the given type and subtype. + * + * @param type the type; never {@code null} + * @param subtype the subtype; never {@code null} + * @return the media type + */ + public static MediaType create(String type, String subtype) { + Preconditions.notNull(type, "type must not be null"); + Preconditions.notNull(subtype, "subtype must not be null"); + return new MediaType(type + "/" + subtype); + } + + /** + * Create a media type with the given type, subtype, and charset. + * + * @param type the type; never {@code null} + * @param subtype the subtype; never {@code null} + * @param charset the charset; never {@code null} + * @return the media type + */ + public static MediaType create(String type, String subtype, Charset charset) { + Preconditions.notNull(type, "type must not be null"); + Preconditions.notNull(subtype, "subtype must not be null"); + Preconditions.notNull(charset, "charset must not be null"); + return new MediaType(type + "/" + subtype + "; charset=" + charset.name()); + } + + private MediaType(String value) { + Matcher matcher = PATTERN.matcher(Preconditions.notNull(value, "value must not be null")); + Preconditions.condition(matcher.matches(), () -> "Invalid media type: '" + value + "'"); + this.value = value; + } + + /** + * {@return string representation of this media type} + */ + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + MediaType that = (MediaType) o; + return Objects.equals(this.value, that.value); + } + + @Override + public int hashCode() { + return Objects.hashCode(value); + } +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java index d8ae7d8f18e0..844b7aed9592 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java index 53865320a86d..31c9730b6222 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolutionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java index 6678d72b898a..093fe276bcf5 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,7 @@ import java.lang.reflect.Parameter; import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance; /** * {@code ParameterResolver} defines the API for {@link Extension Extensions} @@ -30,6 +31,17 @@ * an argument for the parameter must be resolved at runtime by a * {@code ParameterResolver}. * + *

By default, when the methods in this interface are called for a test class + * constructor, the supplied {@link ExtensionContext} represents the test + * class that's about to be instantiated. Extensions may override + * {@link #getTestInstantiationExtensionContextScope} to return + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change + * the scope of the {@code ExtensionContext} to the test method, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. + * Changing the scope makes test-specific data available to the + * implementation of this method and allows keeping state on the test level + * by using the provided {@link ExtensionContext.Store Store} instance. + * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on @@ -44,7 +56,7 @@ * @see TestInstancePreDestroyCallback */ @API(status = STABLE, since = "5.0") -public interface ParameterResolver extends Extension { +public interface ParameterResolver extends TestInstantiationAwareExtension { /** * Determine if this resolver supports resolution of an argument for the diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java new file mode 100644 index 000000000000..03fd96f03800 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptCallback.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * {@code PreInterruptCallback} defines the API for {@link Extension + * Extensions} that wish to be called prior to invocations of + * {@link Thread#interrupt()} by the {@link org.junit.jupiter.api.Timeout} + * extension. + * + *

JUnit registers a default implementation that dumps the stacks of all + * {@linkplain Thread threads} to {@code System.out} if the + * {@value #THREAD_DUMP_ENABLED_PROPERTY_NAME} configuration parameter is set to + * {@code true}. + * + * @since 5.12 + * @see org.junit.jupiter.api.Timeout + */ +@API(status = EXPERIMENTAL, since = "5.12") +public interface PreInterruptCallback extends Extension { + + /** + * Property name used to enable dumping the stack of all + * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred. + * + *

This behavior is disabled by default. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + String THREAD_DUMP_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.timeout.threaddump.enabled"; + + /** + * Callback that is invoked before a {@link Thread} is interrupted with + * {@link Thread#interrupt()}. + * + *

Note: There is no guarantee on which {@link Thread} this callback will be + * executed. + * + * @param preInterruptContext the context with the target {@link Thread}, which will get interrupted. + * @param extensionContext the extension context for the callback; never {@code null} + * @since 5.12 + * @see PreInterruptContext + */ + @API(status = EXPERIMENTAL, since = "5.12") + void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) + throws Exception; +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptContext.java new file mode 100644 index 000000000000..77bb98e728ff --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/PreInterruptContext.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * {@code PreInterruptContext} encapsulates the context in which an + * {@link PreInterruptCallback#beforeThreadInterrupt(PreInterruptContext, ExtensionContext) beforeThreadInterrupt} method is called. + * + * @since 5.12 + * @see PreInterruptCallback + */ +@API(status = EXPERIMENTAL, since = "5.12") +public interface PreInterruptContext { + + /** + * Get the {@link Thread} which will be interrupted. + * + * @return the Thread; never {@code null} + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + Thread getThreadToInterrupt(); +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java index ad074b3e745f..9da713d38353 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ReflectiveInvocationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java index eb84ab641a8f..89121342acb4 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/RegisterExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java index 325ae97ad297..29cb10b53d67 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestExecutionExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java index a5e7e514c540..e06ca4d75c0c 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance; /** * {@code TestInstanceFactory} defines the API for {@link Extension @@ -51,11 +52,21 @@ */ @FunctionalInterface @API(status = STABLE, since = "5.7") -public interface TestInstanceFactory extends Extension { +public interface TestInstanceFactory extends TestInstantiationAwareExtension { /** * Callback for creating a test instance for the supplied context. * + *

By default, the supplied {@link ExtensionContext} represents the test + * class that's about to be instantiated. Extensions may override + * {@link #getTestInstantiationExtensionContextScope} to return + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change + * the scope of the {@code ExtensionContext} to the test method, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. + * Changing the scope makes test-specific data available to the + * implementation of this method and allows keeping state on the test level + * by using the provided {@link ExtensionContext.Store Store} instance. + * *

Note: the {@code ExtensionContext} supplied to a * {@code TestInstanceFactory} will always return an empty * {@link java.util.Optional} value from diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java index 25b8b76755f9..ec2df219ea2e 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstanceFactoryContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java index 1bc5609e81a2..ad70edb7cf41 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance; /** * {@code TestInstancePostProcessor} defines the API for {@link Extension @@ -23,7 +24,9 @@ * etc. * *

Extensions that implement {@code TestInstancePostProcessor} must be - * registered at the class level. + * registered at the class level, {@linkplain ExtendWith declaratively} via a + * field of the test class, or {@linkplain RegisterExtension programmatically} + * via a static field of the test class. * *

Constructor Requirements

* @@ -38,11 +41,21 @@ */ @FunctionalInterface @API(status = STABLE, since = "5.0") -public interface TestInstancePostProcessor extends Extension { +public interface TestInstancePostProcessor extends TestInstantiationAwareExtension { /** * Callback for post-processing the supplied test instance. * + *

By default, the supplied {@link ExtensionContext} represents the test + * class that's being post-processed. Extensions may override + * {@link #getTestInstantiationExtensionContextScope} to return + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change + * the scope of the {@code ExtensionContext} to the test method, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. + * Changing the scope makes test-specific data available to the + * implementation of this method and allows keeping state on the test level + * by using the provided {@link ExtensionContext.Store Store} instance. + * *

Note: the {@code ExtensionContext} supplied to a * {@code TestInstancePostProcessor} will always return an empty * {@link java.util.Optional} value from {@link ExtensionContext#getTestInstance() diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java index 933d7bc9d27b..70a0035c826d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreConstructCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; /** @@ -44,11 +45,21 @@ */ @FunctionalInterface @API(status = STABLE, since = "5.11") -public interface TestInstancePreConstructCallback extends Extension { +public interface TestInstancePreConstructCallback extends TestInstantiationAwareExtension { /** * Callback invoked prior to test instances being constructed. * + *

By default, the supplied {@link ExtensionContext} represents the test + * class that's about to be constructed. Extensions may override + * {@link #getTestInstantiationExtensionContextScope} to return + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} in order to change + * the scope of the {@code ExtensionContext} to the test method, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. + * Changing the scope makes test-specific data available to the + * implementation of this method and allows keeping state on the test level + * by using the provided {@link ExtensionContext.Store Store} instance. + * * @param factoryContext the context for the test instance about to be instantiated; * never {@code null} * @param context the current extension context; never {@code null} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java index a7d27fee7681..2cb6654cfeec 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstancePreDestroyCallback.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java index e38d33b678d3..60b20b6825c2 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstances.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java new file mode 100644 index 000000000000..81bae81f853c --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationAwareExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; + +/** + * Interface for {@link Extension Extensions} that are aware and can influence + * the instantiation of test instances. + * + *

This interface is not indented to be implemented directly. Instead, extend + * one of its sub-interfaces. + * + * @since 5.12 + * @see InvocationInterceptor#interceptTestClassConstructor + * @see ParameterResolver + * @see TestInstancePreConstructCallback + * @see TestInstancePostProcessor + * @see TestInstanceFactory + */ +@API(status = EXPERIMENTAL, since = "5.12") +public interface TestInstantiationAwareExtension extends Extension { + + /** + * Whether this extension should receive a test-scoped + * {@link ExtensionContext} during the instantiation of test instances. + * + *

If an extension returns + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} from this method, + * the following extension methods will be called with a test-scoped + * {@link ExtensionContext} instead of a class-scoped one, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used: + * + *

    + *
  • {@link InvocationInterceptor#interceptTestClassConstructor}
  • + *
  • {@link ParameterResolver} when resolving constructor parameters
  • + *
  • {@link TestInstancePreConstructCallback}
  • + *
  • {@link TestInstancePostProcessor}
  • + *
  • {@link TestInstanceFactory}
  • + *
+ * + *

In such cases, implementations of these extension callbacks can + * observe the following differences: + * + *

    + *
  • {@link ExtensionContext#getElement() getElement()} may refer to the + * test method and {@link ExtensionContext#getTestClass() getTestClass()} + * may refer to a nested test class. + * Use {@link TestInstanceFactoryContext#getTestClass()} to get the class + * under construction.
  • + *
  • {@link ExtensionContext#getTestMethod() getTestMethod()} is no longer + * empty, unless the {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} + * lifecycle is used.
  • + *
  • If the callback adds a new {@link CloseableResource} to the + * {@link Store Store}, the resource is closed just after the instance is + * destroyed.
  • + *
  • The callbacks can now access data previously stored by + * {@link TestTemplateInvocationContext}, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used.
  • + *
+ * + *

Note: The behavior which is enabled by returning + * {@link ExtensionContextScope#TEST_METHOD TEST_METHOD} from this method + * will become the default in future versions of JUnit. To ensure forward + * compatibility, extension implementors are therefore advised to opt in, + * even if they don't require the new functionality. + * + * @implNote There are no guarantees about how often this method is called. + * Therefore, implementations should be idempotent and avoid side + * effects. They may, however, cache the result for performance in + * the {@link Store Store} of the supplied + * {@link ExtensionContext}, if necessary. + * @param rootContext the root extension context to allow inspection of + * configuration parameters; never {@code null} + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.DEFAULT; + } + + /** + * {@code ExtensionContextScope} is used to define the scope of the + * {@link ExtensionContext} passed to an extension during the instantiation + * of test instances. + * + * @since 5.12 + * @see TestInstantiationAwareExtension#getTestInstantiationExtensionContextScope + */ + @API(status = EXPERIMENTAL, since = "5.12") + enum ExtensionContextScope { + + /** + * The extension should receive an {@link ExtensionContext} scoped to + * in the default scope. + * + *

The default scope is determined by the configuration parameter + * {@link #DEFAULT_SCOPE_PROPERTY_NAME}. If not specified, extensions + * will receive an {@link ExtensionContext} scoped to the test class. + * + * @deprecated This behavior will be removed from future versions of + * JUnit and {@link #TEST_METHOD} will become the default. + * + * @see #DEFAULT_SCOPE_PROPERTY_NAME + */ + @API(status = DEPRECATED, since = "5.12") // + @Deprecated + DEFAULT, + + /** + * The extension should receive an {@link ExtensionContext} scoped to + * the test method, unless the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle is used. + */ + TEST_METHOD; + + /** + * Property name used to set the default extension context scope: {@value} + * + *

Supported Values

+ * + *

Supported values include names of enum constants defined in this + * class, ignoring case. + * + * @see #DEFAULT + */ + public static final String DEFAULT_SCOPE_PROPERTY_NAME = "junit.jupiter.extensions.testinstantiation.extensioncontextscope.default"; + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java index 1cccc49a20e1..66884bdba9db 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestInstantiationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java index c75c34ae4f97..37a11b3b923d 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java index 851da77e7cd7..31076236ecae 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestTemplateInvocationContextProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.api.extension; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.stream.Stream; @@ -27,7 +28,7 @@ * preparing the test class instance differently, or multiple times without * modifying the context. * - *

This interface defines two methods: {@link #supportsTestTemplate} and + *

This interface defines two main methods: {@link #supportsTestTemplate} and * {@link #provideTestTemplateInvocationContexts}. The former is called by the * framework to determine whether this extension wants to act on a test template * that is about to be executed. If so, the latter is called and must return a @@ -41,6 +42,10 @@ * the test template method will be invoked using the contexts of all active * providers. * + *

An active provider may return zero invocation contexts from its + * {@link #provideTestTemplateInvocationContexts} method if it overrides + * {@link #mayReturnZeroTestTemplateInvocationContexts} to return {@code true}. + * *

Constructor Requirements

* *

Consult the documentation in {@link Extension} for details on @@ -86,4 +91,27 @@ public interface TestTemplateInvocationContextProvider extends Extension { */ Stream provideTestTemplateInvocationContexts(ExtensionContext context); + /** + * Signal that this provider may provide zero + * {@linkplain TestTemplateInvocationContext invocation contexts} for the test + * template method represented by the supplied {@code context}. + * + *

If this method returns {@code false} (which is the default) and the + * provider returns an empty stream from + * {@link #provideTestTemplateInvocationContexts}, this will be considered + * an execution error. Override this method to return {@code true} to ignore + * the absence of invocation contexts for this provider. + * + * @param context the extension context for the test template method about + * to be invoked; never {@code null} + * @return {@code true} to allow zero contexts, {@code false} to fail + * execution in case of zero contexts + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + default boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext context) { + return false; + } + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java index 176d5fa2fa68..54c7c24da942 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/TestWatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java index 1eb5d6c89f53..25608a4a1ae8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java index c60bd15db337..e4ad90b7ae2f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/Executable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java index 5041f2515777..ca32cf145b02 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingConsumer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java index 892fa15f91d1..42212221844a 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java index 5a7a187278c0..4572b63d9422 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/CleanupMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java index de5fd182d6a8..4657ddd9ead7 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDir.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -28,31 +28,39 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; /** - * {@code @TempDir} can be used to annotate a field in a test class or a - * parameter in a lifecycle method or test method of type {@link Path} or - * {@link File} that should be resolved into a temporary directory. - * - *

Please note that {@code @TempDir} is not supported on constructor - * parameters. Please use field injection instead by annotating an instance - * field with {@code @TempDir}. + * {@code @TempDir} can be used to annotate a field in a test class or a parameter + * in a test class constructor, lifecycle method, or test method of type + * {@link Path} or {@link File} that should be resolved into a temporary directory. * *

Creation

* *

The temporary directory is only created if a field in a test class or a - * parameter in a lifecycle method or test method is annotated with - * {@code @TempDir}. If the field type or parameter type is neither {@link Path} - * nor {@link File}, if a field is declared as {@code final}, or if the temporary - * directory cannot be created, an {@link ExtensionConfigurationException} or a - * {@link ParameterResolutionException} will be thrown as appropriate. In - * addition, a {@code ParameterResolutionException} will be thrown for a - * constructor parameter annotated with {@code @TempDir}. + * parameter in a test class constructor, lifecycle method, or test method is + * annotated with {@code @TempDir}. An {@link ExtensionConfigurationException} or + * a {@link ParameterResolutionException} will be thrown in one of the following + * cases: + * + *

    + *
  • If the field type or parameter type is neither {@code Path} nor {@code File}.
  • + *
  • If a field is declared as {@code final}.
  • + *
  • If the temporary directory cannot be created.
  • + *
  • If the field type or parameter type is {@code File} and a custom + * {@link TempDir#factory() factory} is used, which creates a temporary + * directory that does not belong to the + * {@linkplain java.nio.file.FileSystems#getDefault() default file system}. + *
  • + *
* *

Scope

* - *

By default, a separate temporary directory is created for every - * declaration of the {@code @TempDir} annotation. If you want to share a - * temporary directory across all tests in a test class, you should declare the - * annotation on a {@code static} field or on a parameter of a + *

By default, a separate temporary directory is created for every declaration + * of the {@code @TempDir} annotation. For better isolation when using + * {@link org.junit.jupiter.api.TestInstance.Lifecycle#PER_METHOD @TestInstance(Lifecycle.PER_METHOD)} + * semantics, you can annotate an instance field or a parameter in the test class + * constructor with {@code @TempDir} so that each test method uses a separate + * temporary directory. Alternatively, if you want to share a temporary directory + * across all tests in a test class, you should declare the annotation on a + * {@code static} field or on a parameter of a * {@link org.junit.jupiter.api.BeforeAll @BeforeAll} method. * *

Old behavior

@@ -141,9 +149,10 @@ *

Supported Values

*
    *
  • {@code per_context}: creates a single temporary directory for the - * entire test class or method, depending on where it's first declared + * entire test class or method, depending on where {@code @TempDir} is first + * declared
  • *
  • {@code per_declaration}: creates separate temporary directories for - * each declaration site of the {@code @TempDir} annotation. + * each declaration site of the {@code @TempDir} annotation
  • *
* *

If not specified, the default is {@code per_declaration}. diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java index d375482a759c..d5bbce4a4e2f 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/io/TempDirFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java index adb45cd2fcf4..d1c53ca87d96 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Execution.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,8 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestInstance; /** * {@code @Execution} is used to configure the parallel execution @@ -44,6 +46,12 @@ *

{@value #DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME} overrides * {@value #DEFAULT_EXECUTION_MODE_PROPERTY_NAME} for top-level classes. * + *

The default execution mode is not applied to classes that use the + * {@link TestInstance.Lifecycle#PER_CLASS PER_CLASS} lifecycle or a + * {@link MethodOrderer}. In both cases, test methods in such test classes are + * only executed concurrently if the {@code @Execution(CONCURRENT)} annotation + * is present on the test class or method. + * * @see Isolated * @see ResourceLock * @since 5.3 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java index 136a1a0054ec..119ac5278e99 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ExecutionMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java index 8683cd470969..41eb28dea117 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Isolated.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java index 24cdc2240e21..4bc04d6f6108 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceAccessMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ * * @since 5.3 * @see ResourceLock + * @see ResourceLocksProvider.Lock */ @API(status = STABLE, since = "5.10") public enum ResourceAccessMode { diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java index f9839322b37a..24c85cb268c8 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLock.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.api.parallel; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.ElementType; @@ -20,16 +21,14 @@ import java.lang.annotation.Target; import org.apiguardian.api.API; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; /** * {@code @ResourceLock} is used to declare that the annotated test class or test * method requires access to a shared resource identified by a key. * - *

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

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

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

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

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

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

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

Since JUnit Jupiter 5.12, this annotation supports declaring "static" + * shared resources for direct child nodes via the {@link #target()} + * attribute. Using {@link ResourceLockTarget#CHILDREN} in a class-level annotation + * has the same semantics as adding an annotation with the same {@link #value()} + * and {@link #mode()} to each test method and nested test class declared in the + * annotated class. This may improve parallelization when a test class declares a + * {@link ResourceAccessMode#READ READ} lock, but only a few methods hold + * {@link ResourceAccessMode#READ_WRITE READ_WRITE} lock. Note that + * {@code target = CHILDREN} means that {@link #value()} and {@link #mode()} no + * longer apply to a node declaring the annotation. However, the {@link #providers()} + * attribute remains applicable, and the target of "dynamic" shared resources added + * via implementations of {@link ResourceLocksProvider} is not changed. + * * @see Isolated * @see Resources * @see ResourceAccessMode + * @see ResourceLockTarget * @see ResourceLocks + * @see ResourceLocksProvider * @since 5.3 */ @API(status = STABLE, since = "5.10") @@ -64,9 +88,12 @@ /** * The resource key. * + *

Defaults to an empty string. + * * @see Resources + * @see ResourceLocksProvider.Lock#getKey() */ - String value(); + String value() default ""; /** * The resource access mode. @@ -74,7 +101,33 @@ *

Defaults to {@link ResourceAccessMode#READ_WRITE READ_WRITE}. * * @see ResourceAccessMode + * @see ResourceLocksProvider.Lock#getAccessMode() */ ResourceAccessMode mode() default ResourceAccessMode.READ_WRITE; + /** + * An array of one or more classes implementing {@link ResourceLocksProvider}. + * + *

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

Defaults to {@link ResourceLockTarget#SELF SELF}. + * + *

Note that using {@link ResourceLockTarget#CHILDREN} in a method-level + * annotation results in an exception. + * + * @see ResourceLockTarget + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + ResourceLockTarget target() default ResourceLockTarget.SELF; + } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java new file mode 100644 index 000000000000..94a7a0bf7d68 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLockTarget.java @@ -0,0 +1,49 @@ +/* + * 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.parallel; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import org.apiguardian.api.API; + +/** + * {@code ResourceLockTarget} is used to define the target of a shared resource. + * + * @since 5.12 + * @see ResourceLock#target() + */ +@API(status = EXPERIMENTAL, since = "5.12") +public enum ResourceLockTarget { + + /** + * Add a shared resource to the current node. + */ + SELF, + + /** + * Add a shared resource to the direct children of the current node. + * + *

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

    + *
  • test class: test methods and nested test classes + * declared in the test class are children of the test class.
  • + *
  • nested test class: test methods and nested test + * classes declared in the nested class are children of the nested test class. + *
  • + *
  • test method: test methods are not considered to have + * children. Using {@code CHILDREN} for a test method results in an + * exception.
  • + *
+ */ + CHILDREN + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java index 80588aa9461b..790b4d231b31 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocks.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java new file mode 100644 index 000000000000..2cffc5d6101f --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/ResourceLocksProvider.java @@ -0,0 +1,223 @@ +/* + * 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.parallel; + +import static java.util.Collections.emptySet; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * A {@code ResourceLocksProvider} is used to programmatically add shared resources + * to a test class or its test methods dynamically at runtime. + * + *

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

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

Implementations must provide a no-args constructor. + * + * @since 5.12 + * @see ResourceLock#providers() + * @see Resources + * @see ResourceAccessMode + * @see Lock + */ +@API(status = EXPERIMENTAL, since = "5.12") +public interface ResourceLocksProvider { + + /** + * Add shared resources for a test class. + * + *

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

Invoked in case: + *

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

Invoked in case: + *

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

Each resource is identified by a {@linkplain #getKey() key}. In addition, + * the {@linkplain #getAccessMode() access mode} allows one to specify whether + * a test class or test method requires {@link ResourceAccessMode#READ_WRITE + * READ_WRITE} or {@link ResourceAccessMode#READ READ} access to the resource. + * + * @apiNote {@link #getKey()} and {@link #getAccessMode()} have the same + * semantics as {@link ResourceLock#value()} and {@link ResourceLock#mode()} + * respectively. + * + * @since 5.12 + * @see Isolated + * @see Resources + * @see ResourceAccessMode + * @see ResourceLock + * @see ResourceLocksProvider + */ + final class Lock { + + private final String key; + + private final ResourceAccessMode accessMode; + + /** + * Create a new {@code Lock} with {@link ResourceAccessMode#READ_WRITE}. + * + * @param key the identifier of the resource; never {@code null} or blank + * @see ResourceLock#value() + */ + public Lock(String key) { + this(key, ResourceAccessMode.READ_WRITE); + } + + /** + * Create a new {@code Lock}. + * + * @param key the identifier of the resource; never {@code null} or blank + * @param accessMode the lock mode to use to synchronize access to the + * resource; never {@code null} + * @see ResourceLock#value() + * @see ResourceLock#mode() + */ + public Lock(String key, ResourceAccessMode accessMode) { + this.key = Preconditions.notBlank(key, "key must not be null or blank"); + this.accessMode = Preconditions.notNull(accessMode, "accessMode must not be null"); + } + + /** + * Get the key for this lock. + * + * @see ResourceLock#value() + */ + public String getKey() { + return this.key; + } + + /** + * Get the access mode for this lock. + * + * @see ResourceLock#mode() + */ + public ResourceAccessMode getAccessMode() { + return this.accessMode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Lock that = (Lock) o; + return this.key.equals(that.key) && this.accessMode == that.accessMode; + } + + @Override + public int hashCode() { + return Objects.hash(this.key, this.accessMode); + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("key", this.key) // + .append("accessMode", this.accessMode) // + .toString(); + } + } + +} diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java index c9fa33ff23f0..d874d4b6d391 100644 --- a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/parallel/Resources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ * * @since 5.3 * @see ResourceLock + * @see ResourceLocksProvider.Lock */ @API(status = STABLE, since = "5.10") public class Resources { diff --git a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt index 709aaba4fb1d..ce93a2fdd56b 100644 --- a/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt +++ b/junit-jupiter-api/src/main/kotlin/org/junit/jupiter/api/Assertions.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,8 @@ import java.time.Duration import java.util.function.Supplier import java.util.stream.Stream import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind.AT_MOST_ONCE +import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract /** @@ -30,6 +32,20 @@ fun fail( throwable: Throwable? = null ): Nothing = Assertions.fail(message, throwable) +/** + * @see Assertions.fail + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +@JvmName("fail_nonNullableLambda") +fun fail(message: () -> String): Nothing { + contract { + callsInPlace(message, EXACTLY_ONCE) + } + + return Assertions.fail(message) +} + /** * @see Assertions.fail */ @@ -93,6 +109,154 @@ fun assertAll( vararg executables: () -> Unit ) = assertAll(heading, executables.toList().stream()) +/** + * Example usage: + * ```kotlin + * val nullableString: String? = ... + * + * assertNull(nullableString) + * + * // The compiler won't allow even safe calls, since nullableString is always null. + * // nullableString?.isNotEmpty() + * ``` + * @see Assertions.assertNull + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +fun assertNull(actual: Any?) { + contract { + returns() implies (actual == null) + } + + Assertions.assertNull(actual) +} + +/** + * Example usage: + * ```kotlin + * val nullableString: String? = ... + * + * assertNull(nullableString, "Should be nullable") + * + * // The compiler won't allow even safe calls, since nullableString is always null. + * // nullableString?.isNotEmpty() + * ``` + * @see Assertions.assertNull + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +fun assertNull( + actual: Any?, + message: String +) { + contract { + returns() implies (actual == null) + } + + Assertions.assertNull(actual, message) +} + +/** + * Example usage: + * ```kotlin + * val nullableString: String? = ... + * + * assertNull(nullableString) { "Should be nullable" } + * + * // The compiler won't allow even safe calls, since nullableString is always null. + * // nullableString?.isNotEmpty() + * ``` + * @see Assertions.assertNull + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +fun assertNull( + actual: Any?, + messageSupplier: () -> String +) { + contract { + returns() implies (actual == null) + + callsInPlace(messageSupplier, AT_MOST_ONCE) + } + + Assertions.assertNull(actual, messageSupplier) +} + +/** + * Example usage: + * ```kotlin + * val nullableString: String? = ... + * + * assertNotNull(nullableString) + * + * // The compiler smart casts nullableString to a non-nullable object. + * assertTrue(nullableString.isNotEmpty()) + * ``` + * @see Assertions.assertNotNull + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +fun assertNotNull(actual: Any?) { + contract { + returns() implies (actual != null) + } + + Assertions.assertNotNull(actual) +} + +/** + * Example usage: + * ```kotlin + * val nullableString: String? = ... + * + * assertNotNull(nullableString, "Should be non-nullable") + * + * // The compiler smart casts nullableString to a non-nullable object. + * assertTrue(nullableString.isNotEmpty()) + * ``` + * @see Assertions.assertNotNull + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +fun assertNotNull( + actual: Any?, + message: String +) { + contract { + returns() implies (actual != null) + } + + Assertions.assertNotNull(actual, message) +} + +/** + * Example usage: + * ```kotlin + * val nullableString: String? = ... + * + * assertNotNull(nullableString) { "Should be non-nullable" } + * + * // The compiler smart casts nullableString to a non-nullable object. + * assertTrue(nullableString.isNotEmpty()) + * ``` + * @see Assertions.assertNotNull + */ +@OptIn(ExperimentalContracts::class) +@API(since = "5.12", status = EXPERIMENTAL) +fun assertNotNull( + actual: Any?, + messageSupplier: () -> String +) { + contract { + returns() implies (actual != null) + + callsInPlace(messageSupplier, AT_MOST_ONCE) + } + + Assertions.assertNotNull(actual, messageSupplier) +} + /** * Example usage: * ```kotlin @@ -104,6 +268,8 @@ fun assertAll( * @see Assertions.assertThrows */ inline fun assertThrows(executable: () -> Unit): T { + // no contract for `executable` because it is expected to throw an exception instead + // of being executed completely (see https://youtrack.jetbrains.com/issue/KT-27748) val throwable: Throwable? = try { executable() @@ -143,10 +309,17 @@ inline fun assertThrows( * ``` * @see Assertions.assertThrows */ +@OptIn(ExperimentalContracts::class) inline fun assertThrows( noinline message: () -> String, executable: () -> Unit ): T { + contract { + callsInPlace(message, AT_MOST_ONCE) + // no contract for `executable` because it is expected to throw an exception instead + // of being executed completely (see https://youtrack.jetbrains.com/issue/KT-27748) + } + val throwable: Throwable? = try { executable() @@ -175,8 +348,15 @@ inline fun assertThrows( * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ +@OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") -inline fun assertDoesNotThrow(executable: () -> R): R = Assertions.assertDoesNotThrow(evaluateAndWrap(executable)) +inline fun assertDoesNotThrow(executable: () -> R): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertDoesNotThrow(evaluateAndWrap(executable)) +} /** * Example usage: @@ -188,11 +368,18 @@ inline fun assertDoesNotThrow(executable: () -> R): R = Assertions.assertDoe * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ +@OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow( message: String, executable: () -> R -): R = assertDoesNotThrow({ message }, executable) +): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return assertDoesNotThrow({ message }, executable) +} /** * Example usage: @@ -204,15 +391,22 @@ inline fun assertDoesNotThrow( * @see Assertions.assertDoesNotThrow * @param R the result type of the [executable] */ +@OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") inline fun assertDoesNotThrow( noinline message: () -> String, executable: () -> R -): R = - Assertions.assertDoesNotThrow( +): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(message, AT_MOST_ONCE) + } + + return Assertions.assertDoesNotThrow( evaluateAndWrap(executable), Supplier(message) ) +} @PublishedApi internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier = @@ -231,13 +425,20 @@ internal inline fun evaluateAndWrap(executable: () -> R): ThrowingSupplier assertTimeout( timeout: Duration, executable: () -> R -): R = Assertions.assertTimeout(timeout, executable) +): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertTimeout(timeout, executable) +} /** * Example usage: @@ -247,14 +448,21 @@ fun assertTimeout( * } * ``` * @see Assertions.assertTimeout - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ +@OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeout( timeout: Duration, message: String, executable: () -> R -): R = Assertions.assertTimeout(timeout, executable, message) +): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + } + + return Assertions.assertTimeout(timeout, executable, message) +} /** * Example usage: @@ -264,14 +472,22 @@ fun assertTimeout( * } * ``` * @see Assertions.assertTimeout - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ +@OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeout( timeout: Duration, message: () -> String, executable: () -> R -): R = Assertions.assertTimeout(timeout, executable, message) +): R { + contract { + callsInPlace(executable, EXACTLY_ONCE) + callsInPlace(message, AT_MOST_ONCE) + } + + return Assertions.assertTimeout(timeout, executable, message) +} /** * Example usage: @@ -281,13 +497,16 @@ fun assertTimeout( * } * ``` * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = STABLE, since = "5.11") fun assertTimeoutPreemptively( timeout: Duration, executable: () -> R -): R = Assertions.assertTimeoutPreemptively(timeout, executable) +): R = + // no contract for `executable` because it might be interrupted and throw an exception + // (see https://youtrack.jetbrains.com/issue/KT-27748) + Assertions.assertTimeoutPreemptively(timeout, executable) /** * Example usage: @@ -297,14 +516,17 @@ fun assertTimeoutPreemptively( * } * ``` * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ @API(status = STABLE, since = "5.11") fun assertTimeoutPreemptively( timeout: Duration, message: String, executable: () -> R -): R = Assertions.assertTimeoutPreemptively(timeout, executable, message) +): R = + // no contract for `executable` because it might be interrupted and throw an exception + // (see https://youtrack.jetbrains.com/issue/KT-27748) + Assertions.assertTimeoutPreemptively(timeout, executable, message) /** * Example usage: @@ -314,19 +536,33 @@ fun assertTimeoutPreemptively( * } * ``` * @see Assertions.assertTimeoutPreemptively - * @paramR the result of the [executable]. + * @param R the result of the [executable]. */ +@OptIn(ExperimentalContracts::class) @API(status = STABLE, since = "5.11") fun assertTimeoutPreemptively( timeout: Duration, message: () -> String, executable: () -> R -): R = Assertions.assertTimeoutPreemptively(timeout, executable, message) +): R { + contract { + callsInPlace(message, AT_MOST_ONCE) + // no contract for `executable` because it might be interrupted and throw an exception + // (see https://youtrack.jetbrains.com/issue/KT-27748) + } + + return Assertions.assertTimeoutPreemptively(timeout, executable, message) +} /** * Example usage: * ```kotlin - * assertInstanceOf(list, "List should support fast random access") + * val maybeString: Any = ... + * + * assertInstanceOf(maybeString) + * + * // The compiler smart casts maybeString to a String object. + * assertTrue(maybeString.isNotEmpty()) * ``` * @see Assertions.assertInstanceOf * @since 5.11 @@ -343,7 +579,16 @@ inline fun assertInstanceOf( return Assertions.assertInstanceOf(T::class.java, actualValue, message) } -/* +/** + * Example usage: + * ```kotlin + * val maybeString: Any = ... + * + * assertInstanceOf(maybeString) { "Should be a string" } + * + * // The compiler smart casts maybeString to a String object. + * assertTrue(maybeString.isNotEmpty()) + * ``` * @see Assertions.assertInstanceOf * @since 5.11 */ @@ -355,6 +600,8 @@ inline fun assertInstanceOf( ): T { contract { returns() implies (actualValue is T) + + callsInPlace(message, AT_MOST_ONCE) } return Assertions.assertInstanceOf(T::class.java, actualValue, message) } diff --git a/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java b/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java index b6856c78a11e..a9d9249f0eb1 100644 --- a/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java +++ b/junit-jupiter-api/src/module/org.junit.jupiter.api/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 @@ -9,7 +9,9 @@ */ /** - * Defines JUnit Jupiter API for writing tests. + * Defines the JUnit Jupiter API for writing tests. + * + * @since 5.0 */ module org.junit.jupiter.api { requires static transitive org.apiguardian.api; @@ -19,6 +21,7 @@ exports org.junit.jupiter.api; exports org.junit.jupiter.api.condition; exports org.junit.jupiter.api.extension; + exports org.junit.jupiter.api.extension.support; exports org.junit.jupiter.api.function; exports org.junit.jupiter.api.io; exports org.junit.jupiter.api.parallel; diff --git a/junit-jupiter-api/src/nativeImage/initialize-at-build-time b/junit-jupiter-api/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..b8fb5c3d7514 --- /dev/null +++ b/junit-jupiter-api/src/nativeImage/initialize-at-build-time @@ -0,0 +1,4 @@ +org.junit.jupiter.api.DisplayNameGenerator$Standard +org.junit.jupiter.api.TestInstance$Lifecycle +org.junit.jupiter.api.condition.OS +org.junit.jupiter.api.extension.ConditionEvaluationResult diff --git a/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte b/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte index 1a8598d9e980..f664e0df2e74 100644 --- a/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte +++ b/junit-jupiter-api/src/templates/resources/main/org/junit/jupiter/api/condition/JRE.java.jte @@ -7,24 +7,27 @@ ${licenseHeader} package org.junit.jupiter.api.condition; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.reflect.Method; -import java.util.EnumSet; import org.apiguardian.api.API; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.StringUtils; /** * Enumeration of Java Runtime Environment (JRE) versions. * - *

If the current JRE version cannot be detected — for example, if the - * {@code java.version} JVM system property is undefined — then none of - * the constants defined in this enum will be considered to be the - * {@linkplain #isCurrentVersion current JRE version}. + *

If the current JRE version can be detected but is not one of the predefined + * constants in this enum, {@link #OTHER} will be considered to be the + * {@linkplain #isCurrentVersion current JRE version}. If the current JRE version + * cannot be detected — for example, if the {@code java.version} JVM system + * property is undefined — {@link #UNDEFINED} will be considered to be the + * current JRE version. * * @since 5.1 @for(JRE jre : jres)<%-- @@ -38,6 +41,19 @@ import org.junit.platform.commons.util.StringUtils; */ @API(status = STABLE, since = "5.1") public enum JRE { + + /** + * An undefined JRE version. + * + *

This constant is used by JUnit as a default configuration value but is + * not intended to be used by users. + * + *

This constant returns {@code -1} for its {@linkplain #version() version}. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + UNDEFINED(-1), @for(var jre : jres) /** * Java ${jre.getVersion()}. @@ -49,7 +65,7 @@ public enum JRE { @if(jre.getSince() != null)<%-- --%>@API(status = STABLE, since = "${jre.getSince()}") @endif<%-- ---%>JAVA_${jre.getVersion()}, +--%>JAVA_${jre.getVersion()}(${jre.getVersion()}), @endfor /** * A JRE version other than <%-- @@ -60,14 +76,24 @@ public enum JRE { --%>@if(jre.getIndex() % 3 == 1 && !jre.isLast()) * @elseif(!jre.isLast()) @endif<%-- --%>@endfor + * + *

This constant returns {@link Integer#MAX_VALUE} for its + * {@linkplain #version() version}. To retrieve the actual version number, + * use {@link #currentVersionNumber()}. */ - OTHER; + OTHER(Integer.MAX_VALUE); + + static final int UNDEFINED_VERSION = -1; + + static final int MINIMUM_VERSION = 8; private static final Logger logger = LoggerFactory.getLogger(JRE.class); - private static final JRE CURRENT_VERSION = determineCurrentVersion(); + private static final int CURRENT_VERSION = determineCurrentVersion(); + + private static final JRE CURRENT_JRE = determineCurrentJre(CURRENT_VERSION); - private static JRE determineCurrentVersion() { + private static int determineCurrentVersion() { String javaVersion = System.getProperty("java.version"); boolean javaVersionIsBlank = StringUtils.isBlank(javaVersion); @@ -77,7 +103,7 @@ public enum JRE { } if (!javaVersionIsBlank && javaVersion.startsWith("1.8")) { - return JAVA_8; + return 8; } try { @@ -85,50 +111,120 @@ public enum JRE { // that returns an instance of java.lang.Runtime.Version which has the // following method: public int major() Method versionMethod = Runtime.class.getMethod("version"); - Object version = ReflectionUtils.invokeMethod(versionMethod, null); + Object version = ReflectionSupport.invokeMethod(versionMethod, null); Method majorMethod = version.getClass().getMethod("major"); - int major = (int) ReflectionUtils.invokeMethod(majorMethod, version); - switch (major) {<%-- - --%>@for(var jre : jres)<%-- - --%>@if(jre.getVersion() != 8) - case ${jre.getVersion()}: - return JAVA_${jre.getVersion()};<%-- - --%>@endif<%-- - --%>@endfor - default: - return OTHER; - } + return (int) ReflectionSupport.invokeMethod(majorMethod, version); } catch (Exception ex) { logger.debug(ex, () -> "Failed to determine the current JRE version via java.lang.Runtime.Version."); } - // null signals that the current JRE version is "unknown" - return null; + return UNDEFINED_VERSION; + } + + private static JRE determineCurrentJre(int currentVersion) { + switch (currentVersion) { + case UNDEFINED_VERSION: + return UNDEFINED;<%-- + --%>@for(var jre : jres) + case ${jre.getVersion()}: + return JAVA_${jre.getVersion()};<%-- + --%>@endfor + default: + return OTHER; + } + } + + private final int version; + + private JRE(int version) { + this.version = version; + } + + /** + * Get the version of this {@code JRE}. + * + *

If this {@code JRE} is {@link #UNDEFINED}, this method returns + * {@code -1}. If this {@code JRE} is {@link #OTHER}, this method returns + * {@link Integer#MAX_VALUE}. + * + * @return the version of this {@code JRE} + * @since 5.12 + * @see Runtime.Version#feature() + * @see #currentVersionNumber() + */ + @API(status = EXPERIMENTAL, since = "5.12") + public int version() { + return this.version; } /** * @return {@code true} if this {@code JRE} is known to be the * Java Runtime Environment version for the currently executing JVM or if - * the version is {@link #OTHER} + * the version is {@link #OTHER} or {@link #UNDEFINED} + * + * @see #currentJre() + * @see #currentVersionNumber() */ public boolean isCurrentVersion() { - return this == CURRENT_VERSION; + return this == CURRENT_JRE; } /** * @return the {@link JRE} for the currently executing JVM, potentially - * {@link #OTHER} + * {@link #OTHER} or {@link #UNDEFINED} * * @since 5.7 + * @see #currentVersionNumber() + * @deprecated in favor of {@link #currentJre()} */ - @API(status = STABLE, since = "5.7") + @API(status = DEPRECATED, since = "5.12") + @Deprecated public static JRE currentVersion() { + return currentJre(); + } + + /** + * @return the {@link JRE} for the currently executing JVM, potentially + * {@link #OTHER} or {@link #UNDEFINED} + * + * @since 5.12 + * @see #currentVersionNumber() + */ + @API(status = STABLE, since = "5.12") + public static JRE currentJre() { + return CURRENT_JRE; + } + + /** + * @return the version number for the currently executing JVM, or {@code -1} + * if the current JVM version could not be determined + * + * @since 5.12 + * @see Runtime.Version#feature() + * @see #currentJre() + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static int currentVersionNumber() { return CURRENT_VERSION; } - static boolean isCurrentVersionWithinRange(JRE min, JRE max) { - return EnumSet.range(min, max).contains(CURRENT_VERSION); + /** + * @return {@code true} if the supplied version number is known to be the + * Java Runtime Environment version for the currently executing JVM or if + * the supplied version number is {@code -1} and the current JVM version + * could not be determined + * + * @since 5.12 + * @see Runtime.Version#feature() + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static boolean isCurrentVersion(int version) { + return version == CURRENT_VERSION; + } + + static boolean isCurrentVersionWithinRange(int min, int max) { + return CURRENT_VERSION >= min && CURRENT_VERSION <= max; } } diff --git a/junit-jupiter-api/src/templates/resources/testFixtures/org/junit/jupiter/api/condition/JavaVersionPredicates.java.jte b/junit-jupiter-api/src/templates/resources/testFixtures/org/junit/jupiter/api/condition/JavaVersionPredicates.java.jte index de731bb4cd8f..41db82bc794e 100644 --- a/junit-jupiter-api/src/templates/resources/testFixtures/org/junit/jupiter/api/condition/JavaVersionPredicates.java.jte +++ b/junit-jupiter-api/src/templates/resources/testFixtures/org/junit/jupiter/api/condition/JavaVersionPredicates.java.jte @@ -9,10 +9,10 @@ package org.junit.jupiter.api.condition; public class JavaVersionPredicates { - private static final String JAVA_VERSION = System.getProperty("java.version"); + private static final int JAVA_VERSION = Runtime.version().feature(); @for(JRE jre : jres) static boolean onJava${jre.getVersion()}() { - return JAVA_VERSION.startsWith("@if(jre.getVersion() == 8)1.@endif${jre.getVersion()}"); + return JAVA_VERSION == ${jre.getVersion()}; } @endfor static boolean onKnownVersion() { diff --git a/junit-jupiter-api/src/test/README.md b/junit-jupiter-api/src/test/README.md new file mode 100644 index 000000000000..86ed9e49af1d --- /dev/null +++ b/junit-jupiter-api/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. diff --git a/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java similarity index 75% rename from platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java rename to junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java index d5693baaea2f..ed33d21ebf3d 100644 --- a/platform-tests/src/test/java/org/junit/platform/AbstractEqualsAndHashCodeTests.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/EqualsAndHashCodeAssertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,19 +8,23 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform; +package org.junit.jupiter.api; import static org.assertj.core.api.Assertions.assertThat; /** - * Abstract base class for unit tests that wish to test + * Assertions for unit tests that wish to test * {@link Object#equals(Object)} and {@link Object#hashCode()}. * * @since 1.3 */ -public abstract class AbstractEqualsAndHashCodeTests { +public class EqualsAndHashCodeAssertions { - protected final void assertEqualsAndHashCode(T equal1, T equal2, T different) { + private EqualsAndHashCodeAssertions() { + } + + @SuppressWarnings("EqualsWithItself") + public static void assertEqualsAndHashCode(T equal1, T equal2, T different) { assertThat(equal1).isNotNull(); assertThat(equal2).isNotNull(); assertThat(different).isNotNull(); diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledOnOpenJ9.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledOnOpenJ9.java index 965de2b7d255..0cda6f2d5a1b 100644 --- a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledOnOpenJ9.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/DisabledOnOpenJ9.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java index a68e59f37d1d..b0028eadd9cb 100644 --- a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/extension/ExtensionContextParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java index 1abdd36632a8..0c675b07dbdd 100644 --- a/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java +++ b/junit-jupiter-api/src/testFixtures/java/org/junit/jupiter/api/fixtures/TrackLogRecords.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts index 917565e77351..819993462c0e 100644 --- a/junit-jupiter-engine/junit-jupiter-engine.gradle.kts +++ b/junit-jupiter-engine/junit-jupiter-engine.gradle.kts @@ -1,10 +1,6 @@ -import org.gradle.api.tasks.PathSensitivity.RELATIVE - plugins { id("junitbuild.kotlin-library-conventions") - id("junitbuild.testing-conventions") - id("junitbuild.code-generator") - groovy + id("junitbuild.native-image-properties") `java-test-fixtures` } @@ -17,26 +13,10 @@ dependencies { compileOnlyApi(libs.apiguardian) - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(projects.junitPlatformTestkit) - testImplementation(testFixtures(projects.junitPlatformCommons)) - testImplementation(kotlin("stdlib")) - testImplementation(libs.jimfs) - testImplementation(libs.junit4) - testImplementation(libs.kotlinx.coroutines) - testImplementation(libs.groovy4) - testImplementation(libs.memoryfilesystem) - testImplementation(testFixtures(projects.junitJupiterApi)) - osgiVerification(projects.junitPlatformLauncher) } tasks { - test { - inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) - systemProperty("developmentVersion", version) - } jar { bundle { val platformVersion: String by rootProject.extra diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java index 1b0636f18d10..51ca2c102ca2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -33,6 +33,7 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.config.JupiterConfiguration; @@ -48,6 +49,80 @@ @API(status = STABLE, since = "5.0") public final class Constants { + /** + * Property name used to include patterns for auto-detecting extensions: {@value} + * + *

Pattern Matching Syntax

+ * + *

If the property value consists solely of an asterisk ({@code *}), all + * extensions will be included. Otherwise, the property value will be treated + * as a comma-separated list of patterns where each individual pattern will be + * matched against the fully qualified class name (FQCN) of each extension. + * Any dot ({@code .}) in a pattern will match against a dot ({@code .}) + * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match + * against one or more characters in a FQCN. All other characters in a pattern + * will be matched one-to-one against a FQCN. + * + *

Examples

+ * + *
    + *
  • {@code *}: includes all extensions. + *
  • {@code org.junit.*}: includes every extension under the {@code org.junit} + * base package and any of its subpackages. + *
  • {@code *.MyExtension}: includes every extension whose simple class name is + * exactly {@code MyExtension}. + *
  • {@code *System*}: includes every extension whose FQCN contains + * {@code System}. + *
  • {@code *System*, *Dev*}: includes every extension whose FQCN contains + * {@code System} or {@code Dev}. + *
  • {@code org.example.MyExtension, org.example.TheirExtension}: includes + * extensions whose FQCN is exactly {@code org.example.MyExtension} or + * {@code org.example.TheirExtension}. + *
+ * + *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. + * + * @see JupiterConfiguration#EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME + */ + public static final String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME; + + /** + * Property name used to exclude patterns for auto-detecting extensions: {@value} + * + *

Pattern Matching Syntax

+ * + *

If the property value consists solely of an asterisk ({@code *}), all + * extensions will be excluded. Otherwise, the property value will be treated + * as a comma-separated list of patterns where each individual pattern will be + * matched against the fully qualified class name (FQCN) of each extension. + * Any dot ({@code .}) in a pattern will match against a dot ({@code .}) + * or a dollar sign ({@code $}) in a FQCN. Any asterisk ({@code *}) will match + * against one or more characters in a FQCN. All other characters in a pattern + * will be matched one-to-one against a FQCN. + * + *

Examples

+ * + *
    + *
  • {@code *}: excludes all extensions. + *
  • {@code org.junit.*}: excludes every extension under the {@code org.junit} + * base package and any of its subpackages. + *
  • {@code *.MyExtension}: excludes every extension whose simple class name is + * exactly {@code MyExtension}. + *
  • {@code *System*}: excludes every extension whose FQCN contains + * {@code System}. + *
  • {@code *System*, *Dev*}: excludes every extension whose FQCN contains + * {@code System} or {@code Dev}. + *
  • {@code org.example.MyExtension, org.example.TheirExtension}: excludes + * extensions whose FQCN is exactly {@code org.example.MyExtension} or + * {@code org.example.TheirExtension}. + *
+ * + *

Note: A class that matches both an inclusion and exclusion pattern will be excluded. + * + * @see JupiterConfiguration#EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME + */ + public static final String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME; + /** * Property name used to provide patterns for deactivating conditions: {@value} * @@ -90,7 +165,7 @@ public final class Constants { * @see #DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME * @see org.junit.jupiter.api.extension.ExecutionCondition */ - public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; + public static final String DEACTIVATE_ALL_CONDITIONS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; /** * Property name used to set the default display name generator class name: {@value} @@ -107,6 +182,17 @@ public final class Constants { */ public static final String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME; + /** + * Property name used to enable dumping the stack of all + * {@linkplain Thread threads} to {@code System.out} when a timeout has occurred. + * + *

This behavior is disabled by default. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static final String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = JupiterConfiguration.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; + /** * Property name used to set the default test instance lifecycle mode: {@value} * @@ -191,7 +277,7 @@ public final class Constants { *

When set to {@code false} the underlying fork-join pool will reject * additional tasks if all available workers are busy and the maximum * pool-size would be exceeded. - + * *

Value must either {@code true} or {@code false}; defaults to {@code true}. * *

Note: This property only takes affect on Java 9+. @@ -369,6 +455,16 @@ public final class Constants { @API(status = EXPERIMENTAL, since = "5.10") public static final String DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME = TempDir.DEFAULT_FACTORY_PROPERTY_NAME; + /** + * Property name used to set the default extension context scope for + * extensions that participate in test instantiation: {@value} + * + * @since 5.12 + * @see org.junit.jupiter.api.extension.TestInstantiationAwareExtension + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static final String DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; + private Constants() { /* no-op */ } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java index 94b13afd4548..0de9bbb308ec 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/JupiterTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -63,8 +63,8 @@ public Optional getArtifactId() { @Override public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) { - JupiterConfiguration configuration = new CachingJupiterConfiguration( - new DefaultJupiterConfiguration(discoveryRequest.getConfigurationParameters())); + JupiterConfiguration configuration = new CachingJupiterConfiguration(new DefaultJupiterConfiguration( + discoveryRequest.getConfigurationParameters(), discoveryRequest.getOutputDirectoryProvider())); JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(uniqueId, configuration); new DiscoverySelectorResolver().resolveSelectors(discoveryRequest, engineDescriptor); return engineDescriptor; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java index 1280c4b12a11..170a8c2be817 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/CachingJupiterConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -26,9 +26,12 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * Caching implementation of the {@link JupiterConfiguration} API. @@ -45,6 +48,11 @@ public CachingJupiterConfiguration(JupiterConfiguration delegate) { this.delegate = delegate; } + @Override + public Predicate> getFilterForAutoDetectedExtensions() { + return delegate.getFilterForAutoDetectedExtensions(); + } + @Override public Optional getRawConfigurationParameter(String key) { return delegate.getRawConfigurationParameter(key); @@ -58,71 +66,88 @@ public Optional getRawConfigurationParameter(String key, Function delegate.isParallelExecutionEnabled()); + __ -> delegate.isParallelExecutionEnabled()); } @Override public boolean isExtensionAutoDetectionEnabled() { return (boolean) cache.computeIfAbsent(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME, - key -> delegate.isExtensionAutoDetectionEnabled()); + __ -> delegate.isExtensionAutoDetectionEnabled()); + } + + @Override + public boolean isThreadDumpOnTimeoutEnabled() { + return (boolean) cache.computeIfAbsent(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME, + __ -> delegate.isThreadDumpOnTimeoutEnabled()); } @Override public ExecutionMode getDefaultExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_EXECUTION_MODE_PROPERTY_NAME, - key -> delegate.getDefaultExecutionMode()); + __ -> delegate.getDefaultExecutionMode()); } @Override public ExecutionMode getDefaultClassesExecutionMode() { return (ExecutionMode) cache.computeIfAbsent(DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, - key -> delegate.getDefaultClassesExecutionMode()); + __ -> delegate.getDefaultClassesExecutionMode()); } @Override public TestInstance.Lifecycle getDefaultTestInstanceLifecycle() { return (TestInstance.Lifecycle) cache.computeIfAbsent(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - key -> delegate.getDefaultTestInstanceLifecycle()); + __ -> delegate.getDefaultTestInstanceLifecycle()); } @SuppressWarnings("unchecked") @Override public Predicate getExecutionConditionFilter() { return (Predicate) cache.computeIfAbsent(DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME, - key -> delegate.getExecutionConditionFilter()); + __ -> delegate.getExecutionConditionFilter()); } @Override public DisplayNameGenerator getDefaultDisplayNameGenerator() { return (DisplayNameGenerator) cache.computeIfAbsent(DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME, - key -> delegate.getDefaultDisplayNameGenerator()); + __ -> delegate.getDefaultDisplayNameGenerator()); } @SuppressWarnings("unchecked") @Override public Optional getDefaultTestMethodOrderer() { return (Optional) cache.computeIfAbsent(DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME, - key -> delegate.getDefaultTestMethodOrderer()); + __ -> delegate.getDefaultTestMethodOrderer()); } @SuppressWarnings("unchecked") @Override public Optional getDefaultTestClassOrderer() { return (Optional) cache.computeIfAbsent(DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME, - key -> delegate.getDefaultTestClassOrderer()); + __ -> delegate.getDefaultTestClassOrderer()); } @Override public CleanupMode getDefaultTempDirCleanupMode() { return (CleanupMode) cache.computeIfAbsent(DEFAULT_CLEANUP_MODE_PROPERTY_NAME, - key -> delegate.getDefaultTempDirCleanupMode()); + __ -> delegate.getDefaultTempDirCleanupMode()); } @SuppressWarnings("unchecked") @Override public Supplier getDefaultTempDirFactorySupplier() { return (Supplier) cache.computeIfAbsent(DEFAULT_FACTORY_PROPERTY_NAME, - key -> delegate.getDefaultTempDirFactorySupplier()); + __ -> delegate.getDefaultTempDirFactorySupplier()); } + @Override + public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { + return (ExtensionContextScope) cache.computeIfAbsent( + DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, + __ -> delegate.getDefaultTestInstantiationExtensionContextScope()); + } + + @Override + public OutputDirectoryProvider getOutputDirectoryProvider() { + return delegate.getOutputDirectoryProvider(); + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java index 83ef7592534f..7f24180acea7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/DefaultJupiterConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -26,12 +26,15 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.platform.commons.util.ClassNamePatternFilterUtils; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * Default implementation of the {@link JupiterConfiguration} API. @@ -62,11 +65,36 @@ public class DefaultJupiterConfiguration implements JupiterConfiguration { private static final InstantiatingConfigurationParameterConverter tempDirFactoryConverter = // new InstantiatingConfigurationParameterConverter<>(TempDirFactory.class, "temp dir factory"); + private static final EnumConfigurationParameterConverter extensionContextScopeConverter = // + new EnumConfigurationParameterConverter<>(ExtensionContextScope.class, "extension context scope"); + private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; - public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters) { + public DefaultJupiterConfiguration(ConfigurationParameters configurationParameters, + OutputDirectoryProvider outputDirectoryProvider) { this.configurationParameters = Preconditions.notNull(configurationParameters, "ConfigurationParameters must not be null"); + this.outputDirectoryProvider = outputDirectoryProvider; + } + + @Override + public Predicate> getFilterForAutoDetectedExtensions() { + String includePattern = getExtensionAutoDetectionIncludePattern(); + String excludePattern = getExtensionAutoDetectionExcludePattern(); + Predicate predicate = ClassNamePatternFilterUtils.includeMatchingClassNames(includePattern) // + .and(ClassNamePatternFilterUtils.excludeMatchingClassNames(excludePattern)); + return clazz -> predicate.test(clazz.getName()); + } + + private String getExtensionAutoDetectionIncludePattern() { + return configurationParameters.get(EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME) // + .orElse(ClassNamePatternFilterUtils.ALL_PATTERN); + } + + private String getExtensionAutoDetectionExcludePattern() { + return configurationParameters.get(EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME) // + .orElse(ClassNamePatternFilterUtils.BLANK); } @Override @@ -89,6 +117,11 @@ public boolean isExtensionAutoDetectionEnabled() { return configurationParameters.getBoolean(EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME).orElse(false); } + @Override + public boolean isThreadDumpOnTimeoutEnabled() { + return configurationParameters.getBoolean(EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME).orElse(false); + } + @Override public ExecutionMode getDefaultExecutionMode() { return executionModeConverter.get(configurationParameters, DEFAULT_EXECUTION_MODE_PROPERTY_NAME, @@ -141,4 +174,15 @@ public Supplier getDefaultTempDirFactorySupplier() { return () -> supplier.get().orElse(TempDirFactory.Standard.INSTANCE); } + @SuppressWarnings("deprecation") + @Override + public ExtensionContextScope getDefaultTestInstantiationExtensionContextScope() { + return extensionContextScopeConverter.get(configurationParameters, + DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, ExtensionContextScope.DEFAULT); + } + + @Override + public OutputDirectoryProvider getOutputDirectoryProvider() { + return outputDirectoryProvider; + } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java index 16e13ed18a4a..19c8222c76ad 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/EnumConfigurationParameterConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java index 36727edfd0ad..c0df1064f0c3 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,7 +16,7 @@ import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.ConfigurationParameters; /** @@ -49,9 +49,9 @@ Supplier> supply(ConfigurationParameters configurationParameters, St } private Supplier> newInstanceSupplier(String className, String key) { - Try> clazz = ReflectionUtils.tryToLoadClass(className); + Try> clazz = ReflectionSupport.tryToLoadClass(className); // @formatter:off - return () -> clazz.andThenTry(ReflectionUtils::newInstance) + return () -> clazz.andThenTry(ReflectionSupport::newInstance) .andThenTry(this.clazz::cast) .ifSuccess(generator -> logSuccessMessage(className, key)) .ifFailure(cause -> logFailureMessage(className, key, cause)) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java index 7a90072363d2..c9b2781ea73e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/config/JupiterConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,10 +23,14 @@ import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.PreInterruptCallback; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDirFactory; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * @since 5.4 @@ -34,15 +38,21 @@ @API(status = INTERNAL, since = "5.4") public interface JupiterConfiguration { + String EXTENSIONS_AUTODETECTION_INCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.include"; + String EXTENSIONS_AUTODETECTION_EXCLUDE_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.exclude"; String DEACTIVATE_CONDITIONS_PATTERN_PROPERTY_NAME = "junit.jupiter.conditions.deactivate"; String PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME = "junit.jupiter.execution.parallel.enabled"; String DEFAULT_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_EXECUTION_MODE_PROPERTY_NAME; String DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME = Execution.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; String EXTENSIONS_AUTODETECTION_ENABLED_PROPERTY_NAME = "junit.jupiter.extensions.autodetection.enabled"; + String EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME = PreInterruptCallback.THREAD_DUMP_ENABLED_PROPERTY_NAME; String DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME = TestInstance.Lifecycle.DEFAULT_LIFECYCLE_PROPERTY_NAME; String DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME = DisplayNameGenerator.DEFAULT_GENERATOR_PROPERTY_NAME; String DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME = MethodOrderer.DEFAULT_ORDER_PROPERTY_NAME; - String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME; + String DEFAULT_TEST_CLASS_ORDER_PROPERTY_NAME = ClassOrderer.DEFAULT_ORDER_PROPERTY_NAME;; + String DEFAULT_TEST_INSTANTIATION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME = ExtensionContextScope.DEFAULT_SCOPE_PROPERTY_NAME; + + Predicate> getFilterForAutoDetectedExtensions(); Optional getRawConfigurationParameter(String key); @@ -52,6 +62,8 @@ public interface JupiterConfiguration { boolean isExtensionAutoDetectionEnabled(); + boolean isThreadDumpOnTimeoutEnabled(); + ExecutionMode getDefaultExecutionMode(); ExecutionMode getDefaultClassesExecutionMode(); @@ -70,4 +82,7 @@ public interface JupiterConfiguration { Supplier getDefaultTempDirFactorySupplier(); + ExtensionContextScope getDefaultTestInstantiationExtensionContextScope(); + + OutputDirectoryProvider getOutputDirectoryProvider(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java index 28f76b92e30b..bf7cb5d9a059 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/AbstractExtensionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,24 +13,36 @@ import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toCollection; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.junit.jupiter.api.extension.ExecutableInvoker; +import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.engine.extension.ExtensionContextInternal; +import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestTag; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; @@ -38,7 +50,7 @@ /** * @since 5.0 */ -abstract class AbstractExtensionContext implements ExtensionContext, AutoCloseable { +abstract class AbstractExtensionContext implements ExtensionContextInternal, AutoCloseable { private static final NamespacedHierarchicalStore.CloseAction CLOSE_RESOURCES = (__, ___, value) -> { if (value instanceof CloseableResource) { @@ -53,19 +65,21 @@ abstract class AbstractExtensionContext implements Ext private final JupiterConfiguration configuration; private final NamespacedHierarchicalStore valuesStore; private final ExecutableInvoker executableInvoker; + private final ExtensionRegistry extensionRegistry; AbstractExtensionContext(ExtensionContext parent, EngineExecutionListener engineExecutionListener, T testDescriptor, - JupiterConfiguration configuration, ExecutableInvoker executableInvoker) { - this.executableInvoker = executableInvoker; + JupiterConfiguration configuration, ExtensionRegistry extensionRegistry) { Preconditions.notNull(testDescriptor, "TestDescriptor must not be null"); Preconditions.notNull(configuration, "JupiterConfiguration must not be null"); - + Preconditions.notNull(extensionRegistry, "ExtensionRegistry must not be null"); + this.executableInvoker = new DefaultExecutableInvoker(this, extensionRegistry); this.parent = parent; this.engineExecutionListener = engineExecutionListener; this.testDescriptor = testDescriptor; this.configuration = configuration; this.valuesStore = createStore(parent); + this.extensionRegistry = extensionRegistry; // @formatter:off this.tags = testDescriptor.getTags().stream() @@ -102,6 +116,59 @@ public void publishReportEntry(Map values) { this.engineExecutionListener.reportingEntryPublished(this.testDescriptor, ReportEntry.from(values)); } + @Override + public void publishFile(String name, MediaType mediaType, ThrowingConsumer action) { + Preconditions.notNull(name, "name must not be null"); + Preconditions.notNull(mediaType, "mediaType must not be null"); + Preconditions.notNull(action, "action must not be null"); + + publishFileEntry(name, action, file -> { + Preconditions.condition(Files.isRegularFile(file), () -> "Published path must be a regular file: " + file); + return FileEntry.from(file, mediaType.toString()); + }); + } + + @Override + public void publishDirectory(String name, ThrowingConsumer action) { + Preconditions.notNull(name, "name must not be null"); + Preconditions.notNull(action, "action must not be null"); + + ThrowingConsumer enhancedAction = path -> { + Files.createDirectory(path); + action.accept(path); + }; + publishFileEntry(name, enhancedAction, file -> { + Preconditions.condition(Files.isDirectory(file), () -> "Published path must be a directory: " + file); + return FileEntry.from(file, null); + }); + } + + private void publishFileEntry(String name, ThrowingConsumer action, + Function fileEntryCreator) { + Path dir = createOutputDirectory(); + Path path = dir.resolve(name); + Preconditions.condition(path.getParent().equals(dir), () -> "name must not contain path separators: " + name); + try { + action.accept(path); + } + catch (Throwable t) { + UnrecoverableExceptions.rethrowIfUnrecoverable(t); + throw new JUnitException("Failed to publish path", t); + } + Preconditions.condition(Files.exists(path), () -> "Published path must exist: " + path); + FileEntry fileEntry = fileEntryCreator.apply(path); + this.engineExecutionListener.fileEntryPublished(this.testDescriptor, fileEntry); + } + + private Path createOutputDirectory() { + try { + return configuration.getOutputDirectoryProvider().createOutputDirectory(this.testDescriptor); + } + catch (IOException e) { + throw new JUnitException("Failed to create output directory", e); + } + } + @Override public Optional getParent() { return Optional.ofNullable(this.parent); @@ -151,6 +218,11 @@ public ExecutableInvoker getExecutableInvoker() { return executableInvoker; } + @Override + public List getExtensions(Class extensionType) { + return extensionRegistry.getExtensions(extensionType); + } + protected abstract Node.ExecutionMode getPlatformExecutionMode(); private ExecutionMode toJupiterExecutionMode(Node.ExecutionMode mode) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java index fd16422a98a0..5c514362e8d9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassBasedTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,7 +15,8 @@ import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromConstructorParameters; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromExecutableParameters; -import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromFields; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromInstanceFields; +import static org.junit.jupiter.engine.descriptor.ExtensionUtils.registerExtensionsFromStaticFields; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterAllMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findAfterEachMethods; import static org.junit.jupiter.engine.descriptor.LifecycleMethodUtils.findBeforeAllMethods; @@ -38,7 +39,6 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionConfigurationException; import org.junit.jupiter.api.extension.ExtensionContext; @@ -55,8 +55,8 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.execution.DefaultTestInstances; +import org.junit.jupiter.engine.execution.ExtensionContextSupplier; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall.VoidMethodInterceptorCall; @@ -74,7 +74,6 @@ import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.ClassSource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** @@ -83,13 +82,14 @@ * @since 5.5 */ @API(status = INTERNAL, since = "5.5") -public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor { +public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor implements ResourceLockAware { private static final InterceptingExecutableInvoker executableInvoker = new InterceptingExecutableInvoker(); private final Class testClass; protected final Set tags; protected final Lifecycle lifecycle; + private final ExclusiveResourceCollector exclusiveResourceCollector; private ExecutionMode defaultChildExecutionMode; private TestInstanceFactory testInstanceFactory; @@ -104,6 +104,7 @@ public abstract class ClassBasedTestDescriptor extends JupiterTestDescriptor { this.tags = getTags(testClass); this.lifecycle = getTestInstanceLifecycle(testClass, configuration); this.defaultChildExecutionMode = (this.lifecycle == Lifecycle.PER_CLASS ? ExecutionMode.SAME_THREAD : null); + this.exclusiveResourceCollector = ExclusiveResourceCollector.from(testClass); } // --- TestDescriptor ------------------------------------------------------ @@ -141,8 +142,8 @@ public void setDefaultChildExecutionMode(ExecutionMode defaultChildExecutionMode } @Override - public Set getExclusiveResources() { - return getExclusiveResourcesFromAnnotation(getTestClass()); + public final ExclusiveResourceCollector getExclusiveResourceCollector() { + return exclusiveResourceCollector; } @Override @@ -152,7 +153,7 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte // Register extensions from static fields here, at the class level but // after extensions registered via @ExtendWith. - registerExtensionsFromFields(registry, this.testClass, null); + registerExtensionsFromStaticFields(registry, this.testClass); // Resolve the TestInstanceFactory at the class level in order to fail // the entire class in case of configuration errors (e.g., more than @@ -175,12 +176,12 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte registerBeforeEachMethodAdapters(registry); registerAfterEachMethodAdapters(registry); this.afterAllMethods.forEach(method -> registerExtensionsFromExecutableParameters(registry, method)); + registerExtensionsFromInstanceFields(registry, this.testClass); ThrowableCollector throwableCollector = createThrowableCollector(); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); ClassExtensionContext extensionContext = new ClassExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), throwableCollector, - executableInvoker); + context.getExecutionListener(), this, this.lifecycle, context.getConfiguration(), registry, + throwableCollector); // @formatter:off return context.extend() @@ -201,8 +202,7 @@ public JupiterEngineExecutionContext before(JupiterEngineExecutionContext contex // and store the instance in the ExtensionContext. ClassExtensionContext extensionContext = (ClassExtensionContext) context.getExtensionContext(); throwableCollector.execute(() -> { - TestInstances testInstances = context.getTestInstancesProvider().getTestInstances( - context.getExtensionRegistry(), throwableCollector); + TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(context); extensionContext.setTestInstances(testInstances); }); } @@ -273,35 +273,38 @@ private TestInstanceFactory resolveTestInstanceFactory(ExtensionRegistry registr } private TestInstancesProvider testInstancesProvider(JupiterEngineExecutionContext parentExecutionContext, - ClassExtensionContext extensionContext) { + ClassExtensionContext ourExtensionContext) { - return (registry, registrar, throwableCollector) -> extensionContext.getTestInstances().orElseGet( - () -> instantiateAndPostProcessTestInstance(parentExecutionContext, extensionContext, registry, registrar, - throwableCollector)); + // For Lifecycle.PER_CLASS, ourExtensionContext.getTestInstances() is used to store the instance. + // Otherwise, extensionContext.getTestInstances() is always empty and we always create a new instance. + return (registry, context) -> ourExtensionContext.getTestInstances().orElseGet( + () -> instantiateAndPostProcessTestInstance(parentExecutionContext, ourExtensionContext, registry, + context)); } private TestInstances instantiateAndPostProcessTestInstance(JupiterEngineExecutionContext parentExecutionContext, - ExtensionContext extensionContext, ExtensionRegistry registry, ExtensionRegistrar registrar, - ThrowableCollector throwableCollector) { + ClassExtensionContext ourExtensionContext, ExtensionRegistry registry, + JupiterEngineExecutionContext context) { - TestInstances instances = instantiateTestClass(parentExecutionContext, registry, registrar, extensionContext, - throwableCollector); - throwableCollector.execute(() -> { + ExtensionContextSupplier extensionContext = ExtensionContextSupplier.create(context.getExtensionContext(), + ourExtensionContext, configuration); + TestInstances instances = instantiateTestClass(parentExecutionContext, extensionContext, registry, context); + context.getThrowableCollector().execute(() -> { invokeTestInstancePostProcessors(instances.getInnermostInstance(), registry, extensionContext); - // In addition, we register extensions from instance fields here since the - // best time to do that is immediately following test class instantiation - // and post processing. - registerExtensionsFromFields(registrar, this.testClass, instances.getInnermostInstance()); + // In addition, we initialize extension registered programmatically from instance fields here + // since the best time to do that is immediately following test class instantiation + // and post-processing. + context.getExtensionRegistry().initializeExtensions(this.testClass, instances.getInnermostInstance()); }); return instances; } protected abstract TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, - ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, - ThrowableCollector throwableCollector); + ExtensionContextSupplier extensionContext, ExtensionRegistry registry, + JupiterEngineExecutionContext context); protected TestInstances instantiateTestClass(Optional outerInstances, ExtensionRegistry registry, - ExtensionContext extensionContext) { + ExtensionContextSupplier extensionContext) { Optional outerInstance = outerInstances.map(TestInstances::getInnermostInstance); invokeTestInstancePreConstructCallbacks(new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), @@ -313,12 +316,14 @@ protected TestInstances instantiateTestClass(Optional outerInstan DefaultTestInstances.of(instance)); } - private Object invokeTestInstanceFactory(Optional outerInstance, ExtensionContext extensionContext) { + private Object invokeTestInstanceFactory(Optional outerInstance, + ExtensionContextSupplier extensionContext) { Object instance; try { + ExtensionContext actualExtensionContext = extensionContext.get(this.testInstanceFactory); instance = this.testInstanceFactory.createTestInstance( - new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), extensionContext); + new DefaultTestInstanceFactoryContext(this.testClass, outerInstance), actualExtensionContext); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); @@ -358,7 +363,7 @@ private Object invokeTestInstanceFactory(Optional outerInstance, Extensi } private Object invokeTestClassConstructor(Optional outerInstance, ExtensionRegistry registry, - ExtensionContext extensionContext) { + ExtensionContextSupplier extensionContext) { Constructor constructor = ReflectionUtils.getDeclaredConstructor(this.testClass); return executableInvoker.invoke(constructor, outerInstance, extensionContext, registry, @@ -366,16 +371,16 @@ private Object invokeTestClassConstructor(Optional outerInstance, Extens } private void invokeTestInstancePreConstructCallbacks(TestInstanceFactoryContext factoryContext, - ExtensionRegistry registry, ExtensionContext context) { - registry.stream(TestInstancePreConstructCallback.class).forEach( - extension -> executeAndMaskThrowable(() -> extension.preConstructTestInstance(factoryContext, context))); + ExtensionRegistry registry, ExtensionContextSupplier context) { + registry.stream(TestInstancePreConstructCallback.class).forEach(extension -> executeAndMaskThrowable( + () -> extension.preConstructTestInstance(factoryContext, context.get(extension)))); } private void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry, - ExtensionContext context) { + ExtensionContextSupplier context) { - registry.stream(TestInstancePostProcessor.class).forEach( - extension -> executeAndMaskThrowable(() -> extension.postProcessTestInstance(instance, context))); + registry.stream(TestInstancePostProcessor.class).forEach(extension -> executeAndMaskThrowable( + () -> extension.postProcessTestInstance(instance, context.get(extension)))); } private void executeAndMaskThrowable(Executable executable) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java index 09156394183f..aace0e86e16d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassExtensionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,10 +15,10 @@ import java.util.Optional; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; @@ -38,21 +38,21 @@ final class ClassExtensionContext extends AbstractExtensionContext> getResourceLocksProviderEvaluator() { + return provider -> provider.provideForClass(getTestClass()); + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java index 1c3337224a6b..72ef0981b728 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultDynamicTestInvocationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java index ad8bf3b33c55..8414ab8c834a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DefaultTestInstanceFactoryContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java index ec36ab6fb881..05f7ae7d1977 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DisplayNameUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,12 +10,16 @@ package org.junit.jupiter.engine.descriptor; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; import java.util.Optional; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.function.Supplier; import org.junit.jupiter.api.DisplayName; @@ -28,9 +32,8 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; /** @@ -89,40 +92,54 @@ static String determineDisplayName(AnnotatedElement element, Supplier di return displayNameSupplier.get(); } - static String determineDisplayNameForMethod(Class testClass, Method testMethod, - JupiterConfiguration configuration) { + static String determineDisplayNameForMethod(Supplier>> enclosingInstanceTypes, Class testClass, + Method testMethod, JupiterConfiguration configuration) { return determineDisplayName(testMethod, - createDisplayNameSupplierForMethod(testClass, testMethod, configuration)); + createDisplayNameSupplierForMethod(enclosingInstanceTypes, testClass, testMethod, configuration)); } static Supplier createDisplayNameSupplierForClass(Class testClass, JupiterConfiguration configuration) { - return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForClass(testClass)); + return createDisplayNameSupplier(Collections::emptyList, testClass, configuration, + (generator, __) -> generator.generateDisplayNameForClass(testClass)); } - static Supplier createDisplayNameSupplierForNestedClass(Class testClass, + static Supplier createDisplayNameSupplierForNestedClass( + Supplier>> enclosingInstanceTypesSupplier, Class testClass, JupiterConfiguration configuration) { - return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForNestedClass(testClass)); + return createDisplayNameSupplier(enclosingInstanceTypesSupplier, testClass, configuration, + (generator, enclosingInstanceTypes) -> generator.generateDisplayNameForNestedClass(enclosingInstanceTypes, + testClass)); } - private static Supplier createDisplayNameSupplierForMethod(Class testClass, Method testMethod, + private static Supplier createDisplayNameSupplierForMethod( + Supplier>> enclosingInstanceTypesSupplier, Class testClass, Method testMethod, JupiterConfiguration configuration) { - return createDisplayNameSupplier(testClass, configuration, - generator -> generator.generateDisplayNameForMethod(testClass, testMethod)); + return createDisplayNameSupplier(enclosingInstanceTypesSupplier, testClass, configuration, + (generator, enclosingInstanceTypes) -> generator.generateDisplayNameForMethod(enclosingInstanceTypes, + testClass, testMethod)); + } + + private static Supplier createDisplayNameSupplier(Supplier>> enclosingInstanceTypesSupplier, + Class testClass, JupiterConfiguration configuration, + BiFunction>, String> generatorFunction) { + return () -> { + List> enclosingInstanceTypes = makeUnmodifiable(enclosingInstanceTypesSupplier.get()); + return findDisplayNameGenerator(enclosingInstanceTypes, testClass) // + .map(it -> generatorFunction.apply(it, enclosingInstanceTypes)) // + .orElseGet(() -> generatorFunction.apply(configuration.getDefaultDisplayNameGenerator(), + enclosingInstanceTypes)); + }; } - private static Supplier createDisplayNameSupplier(Class testClass, JupiterConfiguration configuration, - Function generatorFunction) { - return () -> findDisplayNameGenerator(testClass) // - .map(generatorFunction) // - .orElseGet(() -> generatorFunction.apply(configuration.getDefaultDisplayNameGenerator())); + private static List makeUnmodifiable(List list) { + return list.isEmpty() ? emptyList() : unmodifiableList(list); } - private static Optional findDisplayNameGenerator(Class testClass) { + private static Optional findDisplayNameGenerator(List> enclosingInstanceTypes, + Class testClass) { Preconditions.notNull(testClass, "Test class must not be null"); - return AnnotationUtils.findAnnotation(testClass, DisplayNameGeneration.class, true) // + return findAnnotation(testClass, DisplayNameGeneration.class, enclosingInstanceTypes) // .map(DisplayNameGeneration::value) // .map(displayNameGeneratorClass -> { if (displayNameGeneratorClass == Standard.class) { @@ -137,7 +154,7 @@ private static Optional findDisplayNameGenerator(Class if (displayNameGeneratorClass == IndicativeSentences.class) { return indicativeSentencesGenerator; } - return ReflectionUtils.newInstance(displayNameGeneratorClass); + return ReflectionSupport.newInstance(displayNameGeneratorClass); }); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java index 2ca4525dbf10..96b7d6e132ba 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicContainerTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java index b94a1eda75e5..15b059ca47b4 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicDescendantFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java index d0f33ae7ab01..cd3c292709e2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/DynamicExtensionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,10 +15,10 @@ import java.util.Optional; import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; @@ -26,8 +26,8 @@ class DynamicExtensionContext extends AbstractExtensionContext getAllExclusiveResources( + Function> providerToLocks) { + return Stream.empty(); + } + + @Override + Stream getStaticResourcesFor(ResourceLockTarget target) { + return Stream.empty(); + } + + @Override + Stream getDynamicResources( + Function> providerToLocks) { + return Stream.empty(); + } + }; + + Stream getAllExclusiveResources( + Function> providerToLocks) { + return Stream.concat(getStaticResourcesFor(SELF), getDynamicResources(providerToLocks)); + } + + abstract Stream getStaticResourcesFor(ResourceLockTarget target); + + abstract Stream getDynamicResources( + Function> providerToLocks); + + static ExclusiveResourceCollector from(AnnotatedElement element) { + List annotations = findRepeatableAnnotations(element, ResourceLock.class); + return annotations.isEmpty() ? NO_EXCLUSIVE_RESOURCES : new DefaultExclusiveResourceCollector(annotations); + } + + private static class DefaultExclusiveResourceCollector extends ExclusiveResourceCollector { + + private final List annotations; + private List providers; + + DefaultExclusiveResourceCollector(List annotations) { + this.annotations = annotations; + } + + @Override + Stream getStaticResourcesFor(ResourceLockTarget target) { + return annotations.stream() // + .filter(annotation -> StringUtils.isNotBlank(annotation.value())) // + .filter(annotation -> annotation.target() == target) // + .map(annotation -> new ExclusiveResource(annotation.value(), toLockMode(annotation.mode()))); + } + + @Override + Stream getDynamicResources( + Function> providerToLocks) { + List providers = getProviders(); + if (providers.isEmpty()) { + return Stream.empty(); + } + return providers.stream() // + .map(providerToLocks) // + .flatMap(Collection::stream) // + .map(lock -> new ExclusiveResource(lock.getKey(), toLockMode(lock.getAccessMode()))); + } + + private List getProviders() { + if (this.providers == null) { + this.providers = annotations.stream() // + .flatMap(annotation -> Stream.of(annotation.providers()).map(ReflectionUtils::newInstance)) // + .collect(toUnmodifiableList()); + } + return providers; + } + + private static ExclusiveResource.LockMode toLockMode(ResourceAccessMode mode) { + switch (mode) { + case READ: + return ExclusiveResource.LockMode.READ; + case READ_WRITE: + return ExclusiveResource.LockMode.READ_WRITE; + } + throw new JUnitException("Unknown ResourceAccessMode: " + mode); + } + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java index 0cb68545e454..976e7df23790 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,9 +11,9 @@ package org.junit.jupiter.engine.descriptor; import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.util.ReflectionUtils.getDeclaredConstructor; import static org.junit.platform.commons.util.ReflectionUtils.streamFields; @@ -35,6 +35,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.engine.extension.ExtensionRegistrar; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; @@ -71,60 +73,94 @@ static MutableExtensionRegistry populateNewExtensionRegistryFromExtendWithAnnota Preconditions.notNull(parentRegistry, "Parent ExtensionRegistry must not be null"); Preconditions.notNull(annotatedElement, "AnnotatedElement must not be null"); - return MutableExtensionRegistry.createRegistryFrom(parentRegistry, streamExtensionTypes(annotatedElement)); + return MutableExtensionRegistry.createRegistryFrom(parentRegistry, + streamDeclarativeExtensionTypes(annotatedElement)); } /** - * Register extensions using the supplied registrar from fields in the supplied - * class that are annotated with {@link ExtendWith @ExtendWith} or - * {@link RegisterExtension @RegisterExtension}. + * Register extensions using the supplied registrar from static fields in + * the supplied class that are annotated with {@link ExtendWith @ExtendWith} + * or {@link RegisterExtension @RegisterExtension}. * *

The extensions will be sorted according to {@link Order @Order} semantics * prior to registration. * * @param registrar the registrar with which to register the extensions; never {@code null} * @param clazz the class or interface in which to find the fields; never {@code null} - * @param instance the instance of the supplied class; may be {@code null} - * when searching for {@code static} fields in the class + * @since 5.11 */ - static void registerExtensionsFromFields(ExtensionRegistrar registrar, Class clazz, Object instance) { - Preconditions.notNull(registrar, "ExtensionRegistrar must not be null"); - Preconditions.notNull(clazz, "Class must not be null"); + static void registerExtensionsFromStaticFields(ExtensionRegistrar registrar, Class clazz) { + streamExtensionRegisteringFields(clazz, ModifierSupport::isStatic) // + .forEach(field -> { + List> extensionTypes = streamDeclarativeExtensionTypes(field).collect( + toList()); + boolean isExtendWithPresent = !extensionTypes.isEmpty(); - Predicate predicate = (instance == null ? ReflectionUtils::isStatic : ReflectionUtils::isNotStatic); + if (isExtendWithPresent) { + extensionTypes.forEach(registrar::registerExtension); + } + if (isAnnotated(field, RegisterExtension.class)) { + Extension extension = readAndValidateExtensionFromField(field, null, extensionTypes); + registrar.registerExtension(extension, field); + } + }); + } - streamFields(clazz, predicate, TOP_DOWN)// - .sorted(orderComparator)// + /** + * Register extensions using the supplied registrar from instance fields in + * the supplied class that are annotated with {@link ExtendWith @ExtendWith} + * or {@link RegisterExtension @RegisterExtension}. + * + *

The extensions will be sorted according to {@link Order @Order} semantics + * prior to registration. + * + * @param registrar the registrar with which to register the extensions; never {@code null} + * @param clazz the class or interface in which to find the fields; never {@code null} + * @since 5.11 + */ + static void registerExtensionsFromInstanceFields(ExtensionRegistrar registrar, Class clazz) { + streamExtensionRegisteringFields(clazz, ReflectionUtils::isNotStatic) // .forEach(field -> { - List> extensionTypes = streamExtensionTypes(field).collect(toList()); + List> extensionTypes = streamDeclarativeExtensionTypes(field).collect( + toList()); boolean isExtendWithPresent = !extensionTypes.isEmpty(); - boolean isRegisterExtensionPresent = isAnnotated(field, RegisterExtension.class); + if (isExtendWithPresent) { extensionTypes.forEach(registrar::registerExtension); } - if (isRegisterExtensionPresent) { - tryToReadFieldValue(field, instance).ifSuccess(value -> { - Preconditions.condition(value instanceof Extension, () -> String.format( - "Failed to register extension via @RegisterExtension field [%s]: field value's type [%s] must implement an [%s] API.", - field, (value != null ? value.getClass().getName() : null), Extension.class.getName())); - - if (isExtendWithPresent) { - Class valueType = value.getClass(); - extensionTypes.forEach(extensionType -> { - Preconditions.condition(!extensionType.equals(valueType), - () -> String.format("Failed to register extension via field [%s]. " - + "The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, " - + "but only one registration of a given extension type is permitted.", - field, valueType.getName())); - }); - } - - registrar.registerExtension((Extension) value, field); - }); + if (isAnnotated(field, RegisterExtension.class)) { + registrar.registerUninitializedExtension(clazz, field, + instance -> readAndValidateExtensionFromField(field, instance, extensionTypes)); } }); } + /** + * @since 5.11 + */ + private static Extension readAndValidateExtensionFromField(Field field, Object instance, + List> declarativeExtensionTypes) { + Object value = tryToReadFieldValue(field, instance) // + .getOrThrow(e -> new PreconditionViolationException( + String.format("Failed to read @RegisterExtension field [%s]", field), e)); + + Preconditions.condition(value instanceof Extension, () -> String.format( + "Failed to register extension via @RegisterExtension field [%s]: field value's type [%s] must implement an [%s] API.", + field, (value != null ? value.getClass().getName() : null), Extension.class.getName())); + + declarativeExtensionTypes.forEach(extensionType -> { + Class valueType = value.getClass(); + Preconditions.condition(!extensionType.equals(valueType), + () -> String.format( + "Failed to register extension via field [%s]. " + + "The field registers an extension of type [%s] via @RegisterExtension and @ExtendWith, " + + "but only one registration of a given extension type is permitted.", + field, valueType.getName())); + }); + + return (Extension) value; + } + /** * Register extensions using the supplied registrar from parameters in the * declared constructor of the supplied class that are annotated with @@ -157,22 +193,32 @@ static void registerExtensionsFromExecutableParameters(ExtensionRegistrar regist // @formatter:off Arrays.stream(executable.getParameters()) .map(parameter -> findRepeatableAnnotations(parameter, index.getAndIncrement(), ExtendWith.class)) - .flatMap(ExtensionUtils::streamExtensionTypes) + .flatMap(ExtensionUtils::streamDeclarativeExtensionTypes) .forEach(registrar::registerExtension); // @formatter:on } /** - * @since 5.8 + * @since 5.11 */ - private static Stream> streamExtensionTypes(AnnotatedElement annotatedElement) { - return streamExtensionTypes(findRepeatableAnnotations(annotatedElement, ExtendWith.class)); + private static Stream streamExtensionRegisteringFields(Class clazz, Predicate predicate) { + return streamFields(clazz, predicate.and(registersExtension), TOP_DOWN)// + .sorted(orderComparator); } /** - * @since 5.8 + * @since 5.11 */ - private static Stream> streamExtensionTypes(List extendWithAnnotations) { + private static Stream> streamDeclarativeExtensionTypes( + AnnotatedElement annotatedElement) { + return streamDeclarativeExtensionTypes(findRepeatableAnnotations(annotatedElement, ExtendWith.class)); + } + + /** + * @since 5.11 + */ + private static Stream> streamDeclarativeExtensionTypes( + List extendWithAnnotations) { return extendWithAnnotations.stream().map(ExtendWith::value).flatMap(Arrays::stream); } @@ -189,4 +235,14 @@ private static int getOrder(Field field) { return findAnnotation(field, Order.class).map(Order::value).orElse(Order.DEFAULT); } + /** + * {@link Predicate} which determines if a {@link Field} registers an extension via + * {@link RegisterExtension @RegisterExtension} or {@link ExtendWith @ExtendWith}. + * + * @since 5.11.3 + */ + private static final Predicate registersExtension = // + field -> isAnnotated(field, RegisterExtension.class) + || !findRepeatableAnnotations(field, ExtendWith.class).isEmpty(); + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java index 61f24b9b070b..e75e69710ddf 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/Filterable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java index 143ca2f14ab2..9491b89fc073 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,10 +14,8 @@ import static org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.toExecutionMode; import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; @@ -53,9 +51,8 @@ public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext conte MutableExtensionRegistry extensionRegistry = MutableExtensionRegistry.createRegistryWithDefaultExtensions( context.getConfiguration()); EngineExecutionListener executionListener = context.getExecutionListener(); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); ExtensionContext extensionContext = new JupiterEngineExtensionContext(executionListener, this, - context.getConfiguration(), executableInvoker); + context.getConfiguration(), extensionRegistry); // @formatter:off return context.extend() diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java index 120789435b6f..6ecd845d6ded 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/JupiterEngineExtensionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,9 +15,9 @@ import java.util.Optional; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; @@ -28,9 +28,9 @@ final class JupiterEngineExtensionContext extends AbstractExtensionContext getExclusiveResourcesFromAnnotation(AnnotatedElement element) { - // @formatter:off - return findRepeatableAnnotations(element, ResourceLock.class).stream() - .map(resource -> new ExclusiveResource(resource.value(), toLockMode(resource.mode()))) - .collect(toSet()); - // @formatter:on - } - - private static LockMode toLockMode(ResourceAccessMode mode) { - switch (mode) { - case READ: - return LockMode.READ; - case READ_WRITE: - return LockMode.READ_WRITE; + @Override + public Set getExclusiveResources() { + if (this instanceof ResourceLockAware) { + return ((ResourceLockAware) this).determineExclusiveResources().collect(toSet()); } - throw new JUnitException("Unknown ResourceAccessMode: " + mode); + return emptySet(); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java index 2d4cea214f3d..7d76ba1a5a16 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package org.junit.jupiter.engine.descriptor; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedMethods; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; import static org.junit.platform.commons.util.ReflectionUtils.returnsPrimitiveVoid; import java.lang.annotation.Annotation; @@ -22,8 +22,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ReflectionUtils; -import org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ModifierSupport; /** * Collection of utilities for working with test lifecycle methods. @@ -81,7 +81,7 @@ private static List findMethodsAndCheckVoidReturnType(Class testClass } private static void assertStatic(Class annotationType, Method method) { - if (ReflectionUtils.isNotStatic(method)) { + if (ModifierSupport.isNotStatic(method)) { throw new JUnitException(String.format( "@%s method '%s' must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS).", annotationType.getSimpleName(), method.toGenericString())); @@ -89,7 +89,7 @@ private static void assertStatic(Class annotationType, Met } private static void assertNonStatic(Class annotationType, Method method) { - if (ReflectionUtils.isStatic(method)) { + if (ModifierSupport.isStatic(method)) { throw new JUnitException(String.format("@%s method '%s' must not be static.", annotationType.getSimpleName(), method.toGenericString())); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java index 8c851955c5b2..3a5b785591f8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodBasedTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,21 +11,28 @@ package org.junit.jupiter.engine.descriptor; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; import static org.junit.jupiter.engine.descriptor.DisplayNameUtils.determineDisplayNameForMethod; +import static org.junit.jupiter.engine.descriptor.ResourceLockAware.enclosingInstanceTypesDependentResourceLocksProviderEvaluator; import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestWatcher; +import org.junit.jupiter.api.parallel.ResourceLocksProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; +import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassUtils; @@ -36,7 +43,6 @@ import org.junit.platform.engine.TestTag; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.support.descriptor.MethodSource; -import org.junit.platform.engine.support.hierarchical.ExclusiveResource; /** * Base class for {@link TestDescriptor TestDescriptors} based on Java methods. @@ -44,7 +50,7 @@ * @since 5.0 */ @API(status = INTERNAL, since = "5.0") -public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor { +public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor implements ResourceLockAware { private static final Logger logger = LoggerFactory.getLogger(MethodBasedTestDescriptor.class); @@ -57,9 +63,9 @@ public abstract class MethodBasedTestDescriptor extends JupiterTestDescriptor { private final Set tags; MethodBasedTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - this(uniqueId, determineDisplayNameForMethod(testClass, testMethod, configuration), testClass, testMethod, - configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + this(uniqueId, determineDisplayNameForMethod(enclosingInstanceTypes, testClass, testMethod, configuration), + testClass, testMethod, configuration); } MethodBasedTestDescriptor(UniqueId uniqueId, String displayName, Class testClass, Method testMethod, @@ -80,8 +86,32 @@ public final Set getTags() { } @Override - public Set getExclusiveResources() { - return getExclusiveResourcesFromAnnotation(getTestMethod()); + public ExclusiveResourceCollector getExclusiveResourceCollector() { + // There's no need to cache this as this method should only be called once + ExclusiveResourceCollector collector = ExclusiveResourceCollector.from(getTestMethod()); + + if (collector.getStaticResourcesFor(CHILDREN).findAny().isPresent()) { + String message = "'ResourceLockTarget.CHILDREN' is not supported for methods." + // + " Invalid method: " + getTestMethod(); + throw new JUnitException(message); + } + + return collector; + } + + @Override + public Function> getResourceLocksProviderEvaluator() { + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, + (provider, enclosingInstanceTypes) -> provider.provideForMethod(enclosingInstanceTypes, getTestClass(), + getTestMethod())); + } + + private List> getEnclosingTestClasses() { + return getParent() // + .filter(ClassBasedTestDescriptor.class::isInstance) // + .map(ClassBasedTestDescriptor.class::cast) // + .map(ClassBasedTestDescriptor::getEnclosingTestClasses) // + .orElseGet(Collections::emptyList); } @Override diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java index 5b681457de84..a6d983e7196b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/MethodExtensionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,10 +15,10 @@ import java.util.Optional; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; @@ -34,9 +34,9 @@ final class MethodExtensionContext extends AbstractExtensionContext testClass, JupiterConfiguration configuration) { - super(uniqueId, testClass, createDisplayNameSupplierForNestedClass(testClass, configuration), configuration); + public NestedClassTestDescriptor(UniqueId uniqueId, Class testClass, + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, + createDisplayNameSupplierForNestedClass(enclosingInstanceTypes, testClass, configuration), configuration); } // --- TestDescriptor ------------------------------------------------------ @@ -63,7 +67,11 @@ public final Set getTags() { @Override public List> getEnclosingTestClasses() { - TestDescriptor parent = getParent().orElse(null); + return getEnclosingTestClasses(getParent().orElse(null)); + } + + @API(status = INTERNAL, since = "5.12") + public static List> getEnclosingTestClasses(TestDescriptor parent) { if (parent instanceof ClassBasedTestDescriptor) { ClassBasedTestDescriptor parentClassDescriptor = (ClassBasedTestDescriptor) parent; List> result = new ArrayList<>(parentClassDescriptor.getEnclosingTestClasses()); @@ -77,14 +85,20 @@ public List> getEnclosingTestClasses() { @Override protected TestInstances instantiateTestClass(JupiterEngineExecutionContext parentExecutionContext, - ExtensionRegistry registry, ExtensionRegistrar registrar, ExtensionContext extensionContext, - ThrowableCollector throwableCollector) { + ExtensionContextSupplier extensionContext, ExtensionRegistry registry, + JupiterEngineExecutionContext context) { // Extensions registered for nested classes and below are not to be used for instantiating and initializing outer classes ExtensionRegistry extensionRegistryForOuterInstanceCreation = parentExecutionContext.getExtensionRegistry(); TestInstances outerInstances = parentExecutionContext.getTestInstancesProvider().getTestInstances( - extensionRegistryForOuterInstanceCreation, registrar, throwableCollector); + extensionRegistryForOuterInstanceCreation, context); return instantiateTestClass(Optional.of(outerInstances), registry, extensionContext); } + @Override + public Function> getResourceLocksProviderEvaluator() { + return enclosingInstanceTypesDependentResourceLocksProviderEvaluator(this::getEnclosingTestClasses, (provider, + enclosingInstanceTypes) -> provider.provideForNestedClass(enclosingInstanceTypes, getTestClass())); + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java new file mode 100644 index 000000000000..c66398105f4b --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ResourceLockAware.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableList; +import static org.junit.jupiter.api.parallel.ResourceLockTarget.CHILDREN; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.junit.jupiter.api.parallel.ResourceLocksProvider; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; + +/** + * @since 5.12 + */ +interface ResourceLockAware extends TestDescriptor { + + default Stream determineExclusiveResources() { + + Deque ancestors = new ArrayDeque<>(); + TestDescriptor parent = this.getParent().orElse(null); + while (parent instanceof ResourceLockAware) { + ancestors.addFirst((ResourceLockAware) parent); + parent = parent.getParent().orElse(null); + } + + Function> evaluator = getResourceLocksProviderEvaluator(); + + if (ancestors.isEmpty()) { + return determineOwnExclusiveResources(evaluator); + } + + Stream parentStaticResourcesForChildren = ancestors.getLast() // + .getExclusiveResourceCollector().getStaticResourcesFor(CHILDREN); + + Stream ancestorDynamicResources = ancestors.stream() // + .map(ResourceLockAware::getExclusiveResourceCollector) // + .flatMap(collector -> collector.getDynamicResources(evaluator)); + + return Stream.of(ancestorDynamicResources, parentStaticResourcesForChildren, + determineOwnExclusiveResources(evaluator))// + .flatMap(s -> s); + } + + default Stream determineOwnExclusiveResources( + Function> providerToLocks) { + return this.getExclusiveResourceCollector().getAllExclusiveResources(providerToLocks); + } + + ExclusiveResourceCollector getExclusiveResourceCollector(); + + Function> getResourceLocksProviderEvaluator(); + + static Function> enclosingInstanceTypesDependentResourceLocksProviderEvaluator( + Supplier>> enclosingInstanceTypesSupplier, + BiFunction>, Set> evaluator) { + return new Function>() { + + private List> enclosingInstanceTypes; + + @Override + public Set apply(ResourceLocksProvider provider) { + if (this.enclosingInstanceTypes == null) { + this.enclosingInstanceTypes = makeUnmodifiable(enclosingInstanceTypesSupplier.get()); + } + return evaluator.apply(provider, this.enclosingInstanceTypes); + } + + private List makeUnmodifiable(List list) { + return list.isEmpty() ? emptyList() : unmodifiableList(list); + } + }; + } + +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java index 1a86ba2e8d9a..f0d37814bbf2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.net.URI; import java.util.Iterator; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Stream; @@ -63,8 +64,8 @@ public class TestFactoryTestDescriptor extends TestMethodTestDescriptor implemen private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); public TestFactoryTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); } // --- Filterable ---------------------------------------------------------- diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java index 56378f766684..d7e653b82418 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,7 +15,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.Preconditions; /** @@ -37,7 +37,7 @@ static TestInstance.Lifecycle getTestInstanceLifecycle(Class testClass, Jupit Preconditions.notNull(configuration, "configuration must not be null"); // @formatter:off - return AnnotationUtils.findAnnotation(testClass, TestInstance.class) + return AnnotationSupport.findAnnotation(testClass, TestInstance.class) .map(TestInstance::value) .orElseGet(configuration::getDefaultTestInstanceLifecycle); // @formatter:on diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java index 80a1ee2ae876..67fa136a52d1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestMethodTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,6 +17,8 @@ import static org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder; import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -24,7 +26,6 @@ import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.InvocationInterceptor; @@ -36,7 +37,6 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.AfterEachMethodAdapter; import org.junit.jupiter.engine.execution.BeforeEachMethodAdapter; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker; import org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; @@ -77,8 +77,8 @@ public class TestMethodTestDescriptor extends MethodBasedTestDescriptor { private final ReflectiveInterceptorCall interceptorCall; public TestMethodTestDescriptor(UniqueId uniqueId, Class testClass, Method testMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, testMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, testMethod, enclosingInstanceTypes, configuration); this.interceptorCall = defaultInterceptorCall; } @@ -99,22 +99,20 @@ public Type getType() { public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistry(context); ThrowableCollector throwableCollector = createThrowableCollector(); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); MethodExtensionContext extensionContext = new MethodExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, context.getConfiguration(), throwableCollector, executableInvoker); - throwableCollector.execute(() -> { - TestInstances testInstances = context.getTestInstancesProvider().getTestInstances(registry, - throwableCollector); - extensionContext.setTestInstances(testInstances); - }); - + context.getExecutionListener(), this, context.getConfiguration(), registry, throwableCollector); // @formatter:off - return context.extend() + JupiterEngineExecutionContext newContext = context.extend() .withExtensionRegistry(registry) .withExtensionContext(extensionContext) .withThrowableCollector(throwableCollector) .build(); // @formatter:on + throwableCollector.execute(() -> { + TestInstances testInstances = newContext.getTestInstancesProvider().getTestInstances(newContext); + extensionContext.setTestInstances(testInstances); + }); + return newContext; } protected MutableExtensionRegistry populateNewExtensionRegistry(JupiterEngineExecutionContext context) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java index f7d074028c95..ea6df34d7436 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateExtensionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,10 +15,10 @@ import java.util.Optional; import org.junit.jupiter.api.TestInstance.Lifecycle; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.support.hierarchical.Node; @@ -30,10 +30,10 @@ final class TestTemplateExtensionContext extends AbstractExtensionContext getExclusiveResources() { - // @ResourceLock annotations are already collected and returned by the enclosing container + // Resources are already collected and returned by the enclosing container return emptySet(); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java index d02f61def41a..e592ba3a6326 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.engine.descriptor; -import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation; @@ -19,15 +18,15 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import java.util.stream.Stream; import org.apiguardian.api.API; -import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstances; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultExecutableInvoker; import org.junit.jupiter.engine.execution.JupiterEngineExecutionContext; import org.junit.jupiter.engine.extension.ExtensionRegistry; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; @@ -48,8 +47,8 @@ public class TestTemplateTestDescriptor extends MethodBasedTestDescriptor implem private final DynamicDescendantFilter dynamicDescendantFilter = new DynamicDescendantFilter(); public TestTemplateTestDescriptor(UniqueId uniqueId, Class testClass, Method templateMethod, - JupiterConfiguration configuration) { - super(uniqueId, testClass, templateMethod, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + super(uniqueId, testClass, templateMethod, enclosingInstanceTypes, configuration); } // --- Filterable ---------------------------------------------------------- @@ -74,16 +73,15 @@ public boolean mayRegisterTests() { // --- Node ---------------------------------------------------------------- @Override - public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) throws Exception { + public JupiterEngineExecutionContext prepare(JupiterEngineExecutionContext context) { MutableExtensionRegistry registry = populateNewExtensionRegistryFromExtendWithAnnotation( context.getExtensionRegistry(), getTestMethod()); // The test instance should be properly maintained by the enclosing class's ExtensionContext. TestInstances testInstances = context.getExtensionContext().getTestInstances().orElse(null); - ExecutableInvoker executableInvoker = new DefaultExecutableInvoker(context); ExtensionContext extensionContext = new TestTemplateExtensionContext(context.getExtensionContext(), - context.getExecutionListener(), this, context.getConfiguration(), testInstances, executableInvoker); + context.getExecutionListener(), this, context.getConfiguration(), registry, testInstances); // @formatter:off return context.extend() @@ -101,18 +99,36 @@ public JupiterEngineExecutionContext execute(JupiterEngineExecutionContext conte List providers = validateProviders(extensionContext, context.getExtensionRegistry()); AtomicInteger invocationIndex = new AtomicInteger(); - // @formatter:off - providers.stream() - .flatMap(provider -> provider.provideTestTemplateInvocationContexts(extensionContext)) - .map(invocationContext -> createInvocationTestDescriptor(invocationContext, invocationIndex.incrementAndGet())) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(invocationTestDescriptor -> execute(dynamicTestExecutor, invocationTestDescriptor)); - // @formatter:on - validateWasAtLeastInvokedOnce(invocationIndex.get(), providers); + for (TestTemplateInvocationContextProvider provider : providers) { + executeForProvider(provider, invocationIndex, dynamicTestExecutor, extensionContext); + } return context; } + private void executeForProvider(TestTemplateInvocationContextProvider provider, AtomicInteger invocationIndex, + DynamicTestExecutor dynamicTestExecutor, ExtensionContext extensionContext) { + + int initialValue = invocationIndex.get(); + + try (Stream stream = invocationContexts(provider, extensionContext)) { + stream.forEach(invocationContext -> toTestDescriptor(invocationContext, invocationIndex.incrementAndGet()) // + .ifPresent(testDescriptor -> execute(dynamicTestExecutor, testDescriptor))); + } + + Preconditions.condition( + invocationIndex.get() != initialValue + || provider.mayReturnZeroTestTemplateInvocationContexts(extensionContext), + String.format( + "Provider [%s] did not provide any invocation contexts, but was expected to do so. " + + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this.", + provider.getClass().getSimpleName())); + } + + private static Stream invocationContexts( + TestTemplateInvocationContextProvider provider, ExtensionContext extensionContext) { + return provider.provideTestTemplateInvocationContexts(extensionContext); + } + private List validateProviders(ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { @@ -127,8 +143,7 @@ private List validateProviders(ExtensionC TestTemplateInvocationContextProvider.class.getSimpleName(), getTestMethod())); } - private Optional createInvocationTestDescriptor(TestTemplateInvocationContext invocationContext, - int index) { + private Optional toTestDescriptor(TestTemplateInvocationContext invocationContext, int index) { UniqueId uniqueId = getUniqueId().append(TestTemplateInvocationTestDescriptor.SEGMENT_TYPE, "#" + index); if (getDynamicDescendantFilter().test(uniqueId, index - 1)) { return Optional.of(new TestTemplateInvocationTestDescriptor(uniqueId, getTestClass(), getTestMethod(), @@ -141,15 +156,4 @@ private void execute(DynamicTestExecutor dynamicTestExecutor, TestDescriptor tes testDescriptor.setParent(this); dynamicTestExecutor.execute(testDescriptor); } - - private void validateWasAtLeastInvokedOnce(int invocationIndex, - List providers) { - - Preconditions.condition(invocationIndex > 0, - () -> "None of the supporting " + TestTemplateInvocationContextProvider.class.getSimpleName() + "s " - + providers.stream().map(provider -> provider.getClass().getSimpleName()).collect( - joining(", ", "[", "]")) - + " provided a non-empty stream"); - } - } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java index 73ad78fe1d93..f782260432d9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractAnnotatedDescriptorWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,7 +15,7 @@ import java.util.List; import java.util.Optional; -import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.TestDescriptor; @@ -49,17 +49,17 @@ public final String getDisplayName() { public final boolean isAnnotated(Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); - return AnnotationUtils.isAnnotated(getAnnotatedElement(), annotationType); + return AnnotationSupport.isAnnotated(getAnnotatedElement(), annotationType); } public final Optional findAnnotation(Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); - return AnnotationUtils.findAnnotation(getAnnotatedElement(), annotationType); + return AnnotationSupport.findAnnotation(getAnnotatedElement(), annotationType); } public final List findRepeatableAnnotations(Class annotationType) { Preconditions.notNull(annotationType, "annotationType must not be null"); - return AnnotationUtils.findRepeatableAnnotations(getAnnotatedElement(), annotationType); + return AnnotationSupport.findRepeatableAnnotations(getAnnotatedElement(), annotationType); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java index 4e014b166bb8..e2da60a53aba 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/AbstractOrderingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java index 142f7f2c08e0..545ee52d5072 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassOrderingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,8 +19,8 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.TestDescriptor; /** @@ -57,9 +57,9 @@ protected DescriptorWrapperOrderer getDescriptorWrapperOrderer( AbstractAnnotatedDescriptorWrapper descriptorWrapper) { AnnotatedElement annotatedElement = descriptorWrapper.getAnnotatedElement(); - return AnnotationUtils.findAnnotation(annotatedElement, TestClassOrder.class)// + return AnnotationSupport.findAnnotation(annotatedElement, TestClassOrder.class)// .map(TestClassOrder::value)// - . map(ReflectionUtils::newInstance)// + . map(ReflectionSupport::newInstance)// .map(this::createDescriptorWrapperOrderer)// .orElse(inheritedDescriptorWrapperOrderer); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index e63656017ad0..0f809dcbde47 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,10 +12,12 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; +import static org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.getEnclosingTestClasses; import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; +import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.support.ReflectionSupport.streamNestedClasses; import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; @@ -35,7 +37,7 @@ import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; import org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass; import org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; @@ -93,7 +95,7 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { UniqueId.Segment lastSegment = uniqueId.getLastSegment(); if (ClassTestDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { String className = lastSegment.getValue(); - return ReflectionUtils.tryToLoadClass(className).toOptional().filter(isTestClassWithTests).map( + return ReflectionSupport.tryToLoadClass(className).toOptional().filter(isTestClassWithTests).map( testClass -> toResolution( context.addToParent(parent -> Optional.of(newClassTestDescriptor(parent, testClass))))).orElse( unresolved()); @@ -103,7 +105,7 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { return toResolution(context.addToParent(() -> selectUniqueId(uniqueId.removeLastSegment()), parent -> { if (parent instanceof ClassBasedTestDescriptor) { Class parentTestClass = ((ClassBasedTestDescriptor) parent).getTestClass(); - return ReflectionUtils.findNestedClasses(parentTestClass, + return ReflectionSupport.findNestedClasses(parentTestClass, isNestedTestClass.and( where(Class::getSimpleName, isEqual(simpleClassName)))).stream().findFirst().flatMap( testClass -> Optional.of(newNestedClassTestDescriptor(parent, testClass))); @@ -121,9 +123,9 @@ private ClassTestDescriptor newClassTestDescriptor(TestDescriptor parent, Class< } private NestedClassTestDescriptor newNestedClassTestDescriptor(TestDescriptor parent, Class testClass) { - return new NestedClassTestDescriptor( - parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, testClass.getSimpleName()), testClass, - configuration); + UniqueId uniqueId = parent.getUniqueId().append(NestedClassTestDescriptor.SEGMENT_TYPE, + testClass.getSimpleName()); + return new NestedClassTestDescriptor(uniqueId, testClass, () -> getEnclosingTestClasses(parent), configuration); } private Resolution toResolution(Optional testDescriptor) { @@ -133,7 +135,7 @@ private Resolution toResolution(Optional tes testClasses.add(testClass); // @formatter:off return Resolution.match(Match.exact(it, () -> { - Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream() + Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod, TOP_DOWN).stream() .map(method -> selectMethod(testClasses, method)); Stream nestedClasses = streamNestedClasses(testClass, isNestedTestClass) .map(nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java index fd51f71b14a8..52bcb48d3e28 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java index f9646db799c5..5868644b4d91 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultClassOrdererContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java index 66931bebb06f..6ce195f046d0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java index de10c94a6ba7..3016f6940fd3 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DefaultMethodOrdererContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java index 2bf17487949b..a828889cc000 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java index 2b2adf3e12b5..59fc6e356012 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodFinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,8 +15,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.0 @@ -34,7 +34,7 @@ Optional findMethod(String methodSpecPart, Class clazz) { String methodName = matcher.group(1); String parameterTypeNames = matcher.group(2); - return ReflectionUtils.findMethod(clazz, methodName, parameterTypeNames); + return ReflectionSupport.findMethod(clazz, methodName, parameterTypeNames); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java index f7d997a5a727..30ee1342b6f5 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodOrderingVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,7 +22,7 @@ import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterTestDescriptor; import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.TestDescriptor; /** @@ -50,7 +50,7 @@ public void visit(TestDescriptor testDescriptor) { private void orderContainedMethods(ClassBasedTestDescriptor classBasedTestDescriptor, Class testClass) { findAnnotation(testClass, TestMethodOrder.class)// .map(TestMethodOrder::value)// - . map(ReflectionUtils::newInstance)// + . map(ReflectionSupport::newInstance)// .map(Optional::of)// .orElseGet(configuration::getDefaultTestMethodOrderer)// .ifPresent(methodOrderer -> { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java index 57df96514da7..9d5af96aa103 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/MethodSelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -166,8 +166,8 @@ private enum MethodType { TEST(new IsTestMethod(), TestMethodTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestMethodTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestMethodTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, configuration); } }, @@ -176,8 +176,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl TestFactoryTestDescriptor.DYNAMIC_TEST_SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestFactoryTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestFactoryTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, + configuration); } }, @@ -185,8 +186,9 @@ protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testCl TestTemplateInvocationTestDescriptor.SEGMENT_TYPE) { @Override protected TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration) { - return new TestTemplateTestDescriptor(uniqueId, testClass, method, configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration) { + return new TestTemplateTestDescriptor(uniqueId, testClass, method, enclosingInstanceTypes, + configuration); } }; @@ -207,7 +209,7 @@ private Optional resolve(List> enclosingClasses, Class< } return context.addToParent(() -> selectClass(enclosingClasses, testClass), // parent -> Optional.of( - createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration))); + createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration))); } private DiscoverySelector selectClass(List> enclosingClasses, Class testClass) { @@ -227,7 +229,7 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq // @formatter:off return methodFinder.findMethod(methodSpecPart, testClass) .filter(methodPredicate) - .map(method -> createTestDescriptor(createUniqueId(method, parent), testClass, method, configuration)); + .map(method -> createTestDescriptor((ClassBasedTestDescriptor) parent, testClass, method, configuration)); // @formatter:on }); } @@ -237,6 +239,12 @@ private Optional resolveUniqueIdIntoTestDescriptor(UniqueId uniq return Optional.empty(); } + private TestDescriptor createTestDescriptor(ClassBasedTestDescriptor parent, Class testClass, Method method, + JupiterConfiguration configuration) { + UniqueId uniqueId = createUniqueId(method, parent); + return createTestDescriptor(uniqueId, testClass, method, parent::getEnclosingTestClasses, configuration); + } + private UniqueId createUniqueId(Method method, TestDescriptor parent) { String methodId = String.format("%s(%s)", method.getName(), ClassUtils.nullSafeToString(method.getParameterTypes())); @@ -244,7 +252,7 @@ private UniqueId createUniqueId(Method method, TestDescriptor parent) { } protected abstract TestDescriptor createTestDescriptor(UniqueId uniqueId, Class testClass, Method method, - JupiterConfiguration configuration); + Supplier>> enclosingInstanceTypes, JupiterConfiguration configuration); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java index 30adff839a04..1e30582d48ca 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,8 +11,8 @@ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.support.ModifierSupport.isPrivate; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; import java.util.function.Predicate; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java index 0039082f1609..3474d8cf0240 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.util.function.Predicate; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java index 8e2074445949..0534cd2481c0 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,9 +11,9 @@ package org.junit.jupiter.engine.discovery.predicates; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isPrivate; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; import java.util.function.Predicate; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java index 49528f285d6d..764ef8356067 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** @@ -51,7 +52,7 @@ private boolean hasTestOrTestFactoryOrTestTemplateMethods(Class candidate) { } private boolean hasNestedTests(Class candidate) { - return !ReflectionUtils.findNestedClasses(candidate, isNestedTestClass).isEmpty(); + return !ReflectionSupport.findNestedClasses(candidate, isNestedTestClass).isEmpty(); } } 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 7a7f5549e7aa..2932640add1a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java index 3f4add6be2f1..8cbc1b7b6cca 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java index 576188582630..fded3a83eab9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java index 7796a047a5f5..7852d382c627 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,10 @@ package org.junit.jupiter.engine.discovery.predicates; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; -import static org.junit.platform.commons.util.ReflectionUtils.isStatic; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; +import static org.junit.platform.commons.support.ModifierSupport.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isPrivate; +import static org.junit.platform.commons.support.ModifierSupport.isStatic; import static org.junit.platform.commons.util.ReflectionUtils.returnsPrimitiveVoid; import java.lang.annotation.Annotation; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java index 32fa951c6330..79443ee7d4be 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/AfterEachMethodAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java index 2ab5db2695c2..2b4ea8658443 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/BeforeEachMethodAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java index e44bf3f2322c..7b9a948205c2 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java index 9c200e0d4423..e1e0a4a89617 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConditionEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java index e2230d139430..79cbd46c6895 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ConstructorInvocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java index 5b913e70eae9..226db46cf82d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultExecutableInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -37,10 +37,6 @@ public DefaultExecutableInvoker(ExtensionContext extensionContext, ExtensionRegi this.extensionRegistry = extensionRegistry; } - public DefaultExecutableInvoker(JupiterEngineExecutionContext context) { - this(context.getExtensionContext(), context.getExtensionRegistry()); - } - @Override public T invoke(Constructor constructor, Object outerInstance) { Object[] arguments = resolveParameters(constructor, Optional.empty(), Optional.ofNullable(outerInstance), diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java index 30b6af17126e..23d2ada54331 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultParameterContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java index 3fec299191f9..60e09379531c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/DefaultTestInstances.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java new file mode 100644 index 000000000000..c813687bb8b4 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ExtensionContextSupplier.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.execution; + +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstantiationAwareExtension; +import org.junit.jupiter.engine.config.JupiterConfiguration; + +/** + * Container of two instances of {@link ExtensionContext} to simplify the legacy for + * #3445. + * + * @since 5.12 + * @see TestInstantiationAwareExtension + */ +@FunctionalInterface +@API(status = INTERNAL, since = "5.12") +public interface ExtensionContextSupplier { + + static ExtensionContextSupplier create(ExtensionContext currentExtensionContext, + ExtensionContext legacyExtensionContext, JupiterConfiguration configuration) { + if (currentExtensionContext == legacyExtensionContext + || configuration.getDefaultTestInstantiationExtensionContextScope() == TEST_METHOD) { + return __ -> currentExtensionContext; + } + return new ScopeBasedExtensionContextSupplier(currentExtensionContext, legacyExtensionContext); + } + + ExtensionContext get(TestInstantiationAwareExtension extension); + + class ScopeBasedExtensionContextSupplier implements ExtensionContextSupplier { + + private final ExtensionContext currentExtensionContext; + private final ExtensionContext legacyExtensionContext; + + private ScopeBasedExtensionContextSupplier(ExtensionContext currentExtensionContext, + ExtensionContext legacyExtensionContext) { + this.currentExtensionContext = currentExtensionContext; + this.legacyExtensionContext = legacyExtensionContext; + } + + public ExtensionContext get(TestInstantiationAwareExtension extension) { + return isTestScoped(extension) ? currentExtensionContext : legacyExtensionContext; + } + + private boolean isTestScoped(TestInstantiationAwareExtension extension) { + ExtensionContext rootContext = legacyExtensionContext.getRoot(); + return extension.getTestInstantiationExtensionContextScope(rootContext) == TEST_METHOD; + } + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java index e81c46c0715b..9454d5829302 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -53,8 +53,9 @@ public class InterceptingExecutableInvoker { * invocation via all registered {@linkplain InvocationInterceptor * interceptors} */ - public T invoke(Constructor constructor, Optional outerInstance, ExtensionContext extensionContext, - ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall, T> interceptorCall) { + public T invoke(Constructor constructor, Optional outerInstance, + ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry, + ReflectiveInterceptorCall, T> interceptorCall) { Object[] arguments = resolveParameters(constructor, Optional.empty(), outerInstance, extensionContext, extensionRegistry); @@ -93,6 +94,14 @@ private T invoke(Invocation originalInvocation, wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, extensionContext)); } + private T invoke(Invocation originalInvocation, + ReflectiveInvocationContext invocationContext, ExtensionContextSupplier extensionContext, + ExtensionRegistry extensionRegistry, ReflectiveInterceptorCall call) { + return interceptorChain.invoke(originalInvocation, extensionRegistry, + (interceptor, wrappedInvocation) -> call.apply(interceptor, wrappedInvocation, invocationContext, + extensionContext.get(interceptor))); + } + public interface ReflectiveInterceptorCall { T apply(InvocationInterceptor interceptor, Invocation invocation, diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java index 1c49b1f60ca3..3b73cbc982e7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/InvocationInterceptorChain.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java index ab4a41b69182..dc099bea9f44 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java index 22e1345d88e6..fb3f2e0c2810 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/MethodInvocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,7 +19,7 @@ import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; import org.junit.jupiter.api.extension.ReflectiveInvocationContext; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; class MethodInvocation implements Invocation, ReflectiveInvocationContext { @@ -57,7 +57,7 @@ public List getArguments() { @Override @SuppressWarnings("unchecked") public T proceed() { - return (T) ReflectionUtils.invokeMethod(this.method, this.target.orElse(null), this.arguments); + return (T) ReflectionSupport.invokeMethod(this.method, this.target.orElse(null), this.arguments); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java index 10a6e2b96b64..a39b4a189474 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/NamespaceAwareStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java index 5d6e1b1802cc..65a4ddc0d38d 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/ParameterResolutionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -83,6 +83,12 @@ public static Object[] resolveParameters(Method method, Optional target, */ public static Object[] resolveParameters(Executable executable, Optional target, Optional outerInstance, ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { + return resolveParameters(executable, target, outerInstance, __ -> extensionContext, extensionRegistry); + } + + public static Object[] resolveParameters(Executable executable, Optional target, + Optional outerInstance, ExtensionContextSupplier extensionContext, + ExtensionRegistry extensionRegistry) { Preconditions.notNull(target, "target must not be null"); @@ -106,12 +112,12 @@ public static Object[] resolveParameters(Executable executable, Optional } private static Object resolveParameter(ParameterContext parameterContext, Executable executable, - ExtensionContext extensionContext, ExtensionRegistry extensionRegistry) { + ExtensionContextSupplier extensionContext, ExtensionRegistry extensionRegistry) { try { // @formatter:off List matchingResolvers = extensionRegistry.stream(ParameterResolver.class) - .filter(resolver -> resolver.supportsParameter(parameterContext, extensionContext)) + .filter(resolver -> resolver.supportsParameter(parameterContext, extensionContext.get(resolver))) .collect(toList()); // @formatter:on @@ -133,7 +139,7 @@ private static Object resolveParameter(ParameterContext parameterContext, Execut } ParameterResolver resolver = matchingResolvers.get(0); - Object value = resolver.resolveParameter(parameterContext, extensionContext); + Object value = resolver.resolveParameter(parameterContext, extensionContext.get(resolver)); validateResolvedType(parameterContext.getParameter(), value, executable, resolver); logger.trace(() -> String.format( diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java index bceef6248a67..723af1b807a1 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/execution/TestInstancesProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,10 +14,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.TestInstances; -import org.junit.jupiter.engine.extension.ExtensionRegistrar; import org.junit.jupiter.engine.extension.ExtensionRegistry; -import org.junit.jupiter.engine.extension.MutableExtensionRegistry; -import org.junit.platform.engine.support.hierarchical.ThrowableCollector; /** * @since 5.0 @@ -26,12 +23,10 @@ @API(status = INTERNAL, since = "5.0") public interface TestInstancesProvider { - default TestInstances getTestInstances(MutableExtensionRegistry extensionRegistry, - ThrowableCollector throwableCollector) { - return getTestInstances(extensionRegistry, extensionRegistry, throwableCollector); + default TestInstances getTestInstances(JupiterEngineExecutionContext context) { + return getTestInstances(context.getExtensionRegistry(), context); } - TestInstances getTestInstances(ExtensionRegistry extensionRegistry, ExtensionRegistrar extensionRegistrar, - ThrowableCollector throwableCollector); + TestInstances getTestInstances(ExtensionRegistry extensionRegistry, JupiterEngineExecutionContext executionContext); } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java index 331f591b6d88..07f10d636e4b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/AutoCloseExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package org.junit.jupiter.engine.extension; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -23,7 +23,9 @@ import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; @@ -58,20 +60,20 @@ public void afterAll(ExtensionContext context) { } private static void closeFields(Class testClass, Object testInstance, ThrowableCollector throwableCollector) { - Predicate predicate = (testInstance == null ? ReflectionUtils::isStatic : ReflectionUtils::isNotStatic); - AnnotationUtils.findAnnotatedFields(testClass, AutoClose.class, predicate, BOTTOM_UP).forEach( + Predicate predicate = (testInstance == null ? ModifierSupport::isStatic : ModifierSupport::isNotStatic); + AnnotationSupport.findAnnotatedFields(testClass, AutoClose.class, predicate, BOTTOM_UP).forEach( field -> throwableCollector.execute(() -> closeField(field, testInstance))); } private static void closeField(Field field, Object testInstance) throws Exception { - String methodName = AnnotationUtils.findAnnotation(field, AutoClose.class).get().value(); + String methodName = AnnotationSupport.findAnnotation(field, AutoClose.class).get().value(); Class fieldType = field.getType(); checkCondition(StringUtils.isNotBlank(methodName), "@AutoClose on field %s must specify a method name.", field); checkCondition(!fieldType.isPrimitive(), "@AutoClose is not supported on primitive field %s.", field); checkCondition(!fieldType.isArray(), "@AutoClose is not supported on array field %s.", field); - Object fieldValue = ReflectionUtils.tryToReadFieldValue(field, testInstance).get(); + Object fieldValue = ReflectionSupport.tryToReadFieldValue(field, testInstance).get(); if (fieldValue == null) { logger.warn(() -> String.format("Cannot @AutoClose field %s because it is null.", getQualifiedName(field))); } @@ -88,13 +90,13 @@ private static void invokeCloseMethod(Field field, Object target, String methodN } Class targetType = target.getClass(); - Method closeMethod = ReflectionUtils.findMethod(targetType, methodName).orElseThrow( + Method closeMethod = ReflectionSupport.findMethod(targetType, methodName).orElseThrow( () -> new ExtensionConfigurationException( String.format("Cannot @AutoClose field %s because %s does not define method %s().", getQualifiedName(field), targetType.getName(), methodName))); closeMethod = ReflectionUtils.getInterfaceMethodIfPossible(closeMethod, targetType); - ReflectionUtils.invokeMethod(closeMethod, target); + ReflectionSupport.invokeMethod(closeMethod, target); } private static void checkCondition(boolean condition, String messageFormat, Field field) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultPreInterruptContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultPreInterruptContext.java new file mode 100644 index 000000000000..0e4be93b94e2 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultPreInterruptContext.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import org.junit.jupiter.api.extension.PreInterruptContext; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * @since 5.12 + */ +class DefaultPreInterruptContext implements PreInterruptContext { + private final Thread threadToInterrupt; + + DefaultPreInterruptContext(Thread threadToInterrupt) { + Preconditions.notNull(threadToInterrupt, "threadToInterrupt must not be null"); + this.threadToInterrupt = threadToInterrupt; + } + + @Override + public Thread getThreadToInterrupt() { + return threadToInterrupt; + } + + @Override + public String toString() { + // @formatter:off + return new ToStringBuilder(this) + .append("threadToInterrupt", this.threadToInterrupt) + .toString(); + // @formatter:on + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultRepetitionInfo.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultRepetitionInfo.java index 0141ecf77431..d9ecb54a3a68 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultRepetitionInfo.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultRepetitionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java new file mode 100644 index 000000000000..966bb7ef6744 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DefaultTestReporter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.nio.file.Path; +import java.util.Map; + +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.commons.util.Preconditions; + +/** + * @since 1.12 + */ +class DefaultTestReporter implements TestReporter { + + private final ExtensionContext extensionContext; + + DefaultTestReporter(ExtensionContext extensionContext) { + this.extensionContext = extensionContext; + } + + @Override + public void publishEntry(Map map) { + extensionContext.publishReportEntry(map); + } + + @Override + public void publishFile(String name, MediaType mediaType, ThrowingConsumer action) { + extensionContext.publishFile(name, mediaType, action); + } + + @Override + public void publishDirectory(String name, ThrowingConsumer action) { + Preconditions.notNull(name, "name must not be null"); + Preconditions.notNull(action, "action must not be null"); + extensionContext.publishDirectory(name, action); + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java index 0d06023adfde..5dedc43e9107 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/DisabledCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package org.junit.jupiter.engine.extension; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.AnnotatedElement; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionContextInternal.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionContextInternal.java new file mode 100644 index 000000000000..449c4adad00a --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionContextInternal.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.List; + +import org.apiguardian.api.API; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * {@code ExtensionContextInternal} extends the {@link ExtensionContext} with internal API. + * + * @since 5.12 + * @see ExtensionContext + */ +@API(status = INTERNAL, since = "5.12") +public interface ExtensionContextInternal extends ExtensionContext { + + /** + * Returns a list of registered extension at this context of the passed {@code extensionType}. + * + * @param the extension type + * @param extensionType the extension type + * @return the list of extensions + */ + List getExtensions(Class extensionType); +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java index 8764904451d6..2822fa51ef99 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,8 +12,12 @@ import static org.apiguardian.api.API.Status.INTERNAL; +import java.lang.reflect.Field; +import java.util.function.Function; + import org.apiguardian.api.API; import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.RegisterExtension; /** * An {@code ExtensionRegistrar} is used to register extensions. @@ -45,11 +49,11 @@ public interface ExtensionRegistrar { * {@link org.junit.jupiter.api.extension.ExtendWith @ExtendWith}, the * {@code source} and the {@code extension} should be the same object. * However, if an extension is registered programmatically via - * {@link org.junit.jupiter.api.extension.RegisterExtension @RegisterExtension}, - * the {@code source} object should be the {@link java.lang.reflect.Field} - * that is annotated with {@code @RegisterExtension}. Similarly, if an - * extension is registered programmatically as a lambda expression - * or method reference, the {@code source} object should be the underlying + * {@link RegisterExtension @RegisterExtension}, the {@code source} object + * should be the {@link java.lang.reflect.Field} that is annotated with + * {@code @RegisterExtension}. Similarly, if an extension is registered + * programmatically as a lambda expression or method reference, the + * {@code source} object should be the underlying * {@link java.lang.reflect.Method} that implements the extension API. * * @param extension the extension to register; never {@code null} @@ -68,4 +72,34 @@ public interface ExtensionRegistrar { */ void registerSyntheticExtension(Extension extension, Object source); + /** + * Register an uninitialized extension for the supplied {@code testClass} to + * be initialized using the supplied {@code initializer} when an instance of + * the test class is created. + * + *

Uninitialized extensions are typically registered for fields annotated + * with {@link RegisterExtension @RegisterExtension} that cannot be + * initialized until an instance of the test class is created. Until they + * are initialized, such extensions are not available for use. + * + * @param testClass the test class for which the extension is registered; + * never {@code null} + * @param source the source of the extension; never {@code null} + * @param initializer the initializer function to be used to create the + * extension; never {@code null} + */ + void registerUninitializedExtension(Class testClass, Field source, + Function initializer); + + /** + * Initialize all registered extensions for the supplied {@code testClass} + * using the supplied {@code testInstance}. + * + * @param testClass the test class for which the extensions are initialized; + * never {@code null} + * @param testInstance the test instance to be used to initialize the + * extensions; never {@code null} + */ + void initializeExtensions(Class testClass, Object testInstance); + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java index ccd58b879006..f18d74c894ad 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/ExtensionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java index 3791f83b8c7b..eac99fe980f7 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/MutableExtensionRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,18 +10,26 @@ package org.junit.jupiter.engine.extension; -import static java.util.stream.Stream.concat; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static org.apiguardian.api.API.Status.INTERNAL; +import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apiguardian.api.API; @@ -29,17 +37,14 @@ import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.util.ServiceLoaderUtils; /** * Default, mutable implementation of {@link ExtensionRegistry}. * - *

A registry has a reference to its parent registry, and all lookups are - * performed first in the current registry itself and then recursively in its - * ancestors. - * * @since 5.5 */ @API(status = INTERNAL, since = "5.5") @@ -64,27 +69,64 @@ public class MutableExtensionRegistry implements ExtensionRegistry, ExtensionReg * auto-detected using Java's {@link ServiceLoader} mechanism and automatically * registered after the default extensions. * + *

If the + * {@value org.junit.jupiter.engine.Constants#EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME} + * configuration parameter has been set to {@code true}, the + * {@link PreInterruptThreadDumpPrinter} will be registered. + * * @param configuration configuration parameters used to retrieve the extension * auto-detection flag; never {@code null} * @return a new {@code ExtensionRegistry}; never {@code null} */ public static MutableExtensionRegistry createRegistryWithDefaultExtensions(JupiterConfiguration configuration) { - MutableExtensionRegistry extensionRegistry = new MutableExtensionRegistry(null); + MutableExtensionRegistry extensionRegistry = new MutableExtensionRegistry(); DEFAULT_STATELESS_EXTENSIONS.forEach(extensionRegistry::registerDefaultExtension); extensionRegistry.registerDefaultExtension(new TempDirectory(configuration)); if (configuration.isExtensionAutoDetectionEnabled()) { - registerAutoDetectedExtensions(extensionRegistry); + registerAutoDetectedExtensions(extensionRegistry, configuration); + } + + if (configuration.isThreadDumpOnTimeoutEnabled()) { + extensionRegistry.registerDefaultExtension(new PreInterruptThreadDumpPrinter()); } return extensionRegistry; } - private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry) { - ServiceLoader.load(Extension.class, ClassLoaderUtils.getDefaultClassLoader())// + private static void registerAutoDetectedExtensions(MutableExtensionRegistry extensionRegistry, + JupiterConfiguration configuration) { + + Predicate> filter = configuration.getFilterForAutoDetectedExtensions(); + List> excludedExtensions = new ArrayList<>(); + + ServiceLoader serviceLoader = ServiceLoader.load(Extension.class, + ClassLoaderUtils.getDefaultClassLoader()); + ServiceLoaderUtils.filter(serviceLoader, clazz -> { + boolean included = filter.test(clazz); + if (!included) { + excludedExtensions.add(clazz); + } + return included; + }) // .forEach(extensionRegistry::registerAutoDetectedExtension); + + logExcludedExtensions(excludedExtensions); + } + + private static void logExcludedExtensions(List> excludedExtensions) { + if (!excludedExtensions.isEmpty()) { + // @formatter:off + List excludeExtensionNames = excludedExtensions + .stream() + .map(Class::getName) + .collect(Collectors.toList()); + // @formatter:on + logger.config(() -> String.format( + "Excluded auto-detected extensions due to configured includes/excludes: %s", excludeExtensionNames)); + } } /** @@ -106,44 +148,47 @@ public static MutableExtensionRegistry createRegistryFrom(MutableExtensionRegist return registry; } - private final MutableExtensionRegistry parent; + private final Set> registeredExtensionTypes; + private final List registeredExtensions; + private final Map, LateInitExtensions> lateInitExtensions; - private final Set> registeredExtensionTypes = new LinkedHashSet<>(); - - private final List registeredExtensions = new ArrayList<>(); + private MutableExtensionRegistry() { + this(emptySet(), emptyList()); + } private MutableExtensionRegistry(MutableExtensionRegistry parent) { - this.parent = parent; + this(parent.registeredExtensionTypes, parent.registeredExtensions); } - @Override - public Stream stream(Class extensionType) { - if (this.parent == null) { - return streamLocal(extensionType); - } - return concat(this.parent.stream(extensionType), streamLocal(extensionType)); + private MutableExtensionRegistry(Set> registeredExtensionTypes, + List registeredExtensions) { + this.registeredExtensionTypes = new LinkedHashSet<>(registeredExtensionTypes); + this.registeredExtensions = new ArrayList<>(registeredExtensions.size()); + this.lateInitExtensions = new LinkedHashMap<>(); + registeredExtensions.forEach(entry -> { + Entry newEntry = entry; + if (entry instanceof LateInitEntry) { + LateInitEntry lateInitEntry = (LateInitEntry) entry; + newEntry = lateInitEntry.getExtension() // + .map(Entry::of) // + .orElseGet(() -> getLateInitExtensions(lateInitEntry.getTestClass()).add(lateInitEntry.copy())); + } + this.registeredExtensions.add(newEntry); + }); } - /** - * Stream all {@code Extensions} of the specified type that are present - * in this registry. - * - *

Extensions in ancestors are ignored. - * - * @param extensionType the type of {@link Extension} to stream - */ - private Stream streamLocal(Class extensionType) { - // @formatter:off - return this.registeredExtensions.stream() - .filter(extensionType::isInstance) + @Override + public Stream stream(Class extensionType) { + return this.registeredExtensions.stream() // + .map(p -> p.getExtension().orElse(null)) // + .filter(extensionType::isInstance) // .map(extensionType::cast); - // @formatter:on } @Override public void registerExtension(Class extensionType) { if (!isAlreadyRegistered(extensionType)) { - registerLocalExtension(ReflectionUtils.newInstance(extensionType)); + registerLocalExtension(ReflectionSupport.newInstance(extensionType)); } } @@ -152,8 +197,7 @@ public void registerExtension(Class extensionType) { * parent registry. */ private boolean isAlreadyRegistered(Class extensionType) { - return (this.registeredExtensionTypes.contains(extensionType) - || (this.parent != null && this.parent.isAlreadyRegistered(extensionType))); + return this.registeredExtensionTypes.contains(extensionType); } @Override @@ -167,6 +211,36 @@ public void registerSyntheticExtension(Extension extension, Object source) { registerExtension("synthetic", extension, source); } + @Override + public void registerUninitializedExtension(Class testClass, Field source, + Function initializer) { + Preconditions.notNull(testClass, "testClass must not be null"); + Preconditions.notNull(source, "source must not be null"); + Preconditions.notNull(initializer, "initializer must not be null"); + + logger.trace(() -> String.format("Registering local extension (late-init) for [%s]%s", + source.getType().getName(), buildSourceInfo(source))); + + LateInitEntry entry = getLateInitExtensions(testClass) // + .add(new LateInitEntry(testClass, initializer)); + this.registeredExtensions.add(entry); + } + + @Override + public void initializeExtensions(Class testClass, Object testInstance) { + Preconditions.notNull(testClass, "testClass must not be null"); + Preconditions.notNull(testInstance, "testInstance must not be null"); + + LateInitExtensions extensions = lateInitExtensions.remove(testClass); + if (extensions != null) { + extensions.initialize(testInstance); + } + } + + private LateInitExtensions getLateInitExtensions(Class testClass) { + return this.lateInitExtensions.computeIfAbsent(testClass, __ -> new LateInitExtensions()); + } + private void registerDefaultExtension(Extension extension) { registerExtension("default", extension); } @@ -185,12 +259,12 @@ private void registerExtension(String category, Extension extension) { private void registerExtension(String category, Extension extension, Object source) { Preconditions.notBlank(category, "category must not be null or blank"); - Preconditions.notNull(extension, "Extension must not be null"); + Preconditions.notNull(extension, "extension must not be null"); logger.trace( () -> String.format("Registering %s extension [%s]%s", category, extension, buildSourceInfo(source))); - this.registeredExtensions.add(extension); + this.registeredExtensions.add(Entry.of(extension)); this.registeredExtensionTypes.add(extension.getClass()); } @@ -206,4 +280,62 @@ private String buildSourceInfo(Object source) { return " from source [" + source + "]"; } + private interface Entry { + + static Entry of(Extension extension) { + Optional value = Optional.of(extension); + return () -> value; + } + + Optional getExtension(); + } + + private static class LateInitEntry implements Entry { + + private final Class testClass; + private final Function initializer; + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Optional extension = Optional.empty(); + + public LateInitEntry(Class testClass, Function initializer) { + this.testClass = testClass; + this.initializer = initializer; + } + + @Override + public Optional getExtension() { + return extension; + } + + public Class getTestClass() { + return testClass; + } + + void initialize(Object testInstance) { + Preconditions.condition(!extension.isPresent(), "Extension already initialized"); + extension = Optional.of(initializer.apply(testInstance)); + } + + LateInitEntry copy() { + Preconditions.condition(!extension.isPresent(), "Extension already initialized"); + return new LateInitEntry(testClass, initializer); + } + } + + private static class LateInitExtensions { + + private final List entries = new ArrayList<>(); + + LateInitEntry add(LateInitEntry entry) { + entries.add(entry); + return entry; + } + + void initialize(Object testInstance) { + entries.forEach(entry -> entry.initialize(testInstance)); + } + + } + } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocation.java new file mode 100644 index 000000000000..0484c0f788c4 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocation.java @@ -0,0 +1,24 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.util.function.Consumer; + +/** + * @since 5.12 + */ +@FunctionalInterface +interface PreInterruptCallbackInvocation { + PreInterruptCallbackInvocation NOOP = (t, e) -> { + }; + + void executePreInterruptCallback(Thread threadToInterrupt, Consumer errorHandler); +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocationFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocationFactory.java new file mode 100644 index 000000000000..992a1b721640 --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptCallbackInvocationFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.util.List; + +import org.junit.jupiter.api.extension.PreInterruptCallback; +import org.junit.jupiter.api.extension.PreInterruptContext; +import org.junit.platform.commons.util.UnrecoverableExceptions; + +/** + * @since 5.12 + * @see PreInterruptCallbackInvocation + */ +final class PreInterruptCallbackInvocationFactory { + + private PreInterruptCallbackInvocationFactory() { + } + + static PreInterruptCallbackInvocation create(ExtensionContextInternal extensionContext) { + final List callbacks = extensionContext.getExtensions(PreInterruptCallback.class); + if (callbacks.isEmpty()) { + return PreInterruptCallbackInvocation.NOOP; + } + return (thread, errorHandler) -> { + PreInterruptContext preInterruptContext = new DefaultPreInterruptContext(thread); + for (PreInterruptCallback callback : callbacks) { + try { + callback.beforeThreadInterrupt(preInterruptContext, extensionContext); + } + catch (Throwable ex) { + UnrecoverableExceptions.rethrowIfUnrecoverable(ex); + errorHandler.accept(ex); + } + } + }; + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptThreadDumpPrinter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptThreadDumpPrinter.java new file mode 100644 index 000000000000..40fa9e03e3bf --- /dev/null +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/PreInterruptThreadDumpPrinter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import java.util.Map; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.PreInterruptCallback; +import org.junit.jupiter.api.extension.PreInterruptContext; +import org.junit.jupiter.engine.Constants; + +/** + * The default implementation for {@link PreInterruptCallback}, + * which will print the stacks of all {@link Thread}s to {@code System.out}. + * + *

Note: This is disabled by default, and must be enabled with + * {@link Constants#EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME} + * + * @since 5.12 + */ +final class PreInterruptThreadDumpPrinter implements PreInterruptCallback { + private static final String NL = "\n"; + + @Override + public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) { + + Map stackTraces = Thread.getAllStackTraces(); + + StringBuilder sb = new StringBuilder(); + sb.append("Thread "); + appendThreadName(sb, preInterruptContext.getThreadToInterrupt()); + sb.append(" will be interrupted."); + sb.append(NL); + + for (Map.Entry entry : stackTraces.entrySet()) { + Thread thread = entry.getKey(); + StackTraceElement[] stack = entry.getValue(); + if (stack.length > 0) { + sb.append(NL); + appendThreadName(sb, thread); + for (StackTraceElement stackTraceElement : stack) { + sb.append(NL); + //Do the same prefix as java.lang.Throwable.printStackTrace(java.lang.Throwable.PrintStreamOrWriter) + sb.append("\tat "); + sb.append(stackTraceElement.toString()); + + } + sb.append(NL); + } + } + + System.out.println(sb); + } + + /** + * Appends the {@link Thread} name and ID in a similar fashion as {@code jstack}. + * @param sb the buffer + * @param th the thread to append + */ + private void appendThreadName(StringBuilder sb, Thread th) { + // Use same format as java.lang.management.ThreadInfo.toString + sb.append("\""); + sb.append(th.getName()); + sb.append("\""); + if (th.isDaemon()) { + sb.append(" daemon"); + } + sb.append(" prio="); + sb.append(th.getPriority()); + sb.append(" Id="); + sb.append(th.getId()); + sb.append(" "); + sb.append(th.getState()); + } +} diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java index b351721e4971..3ce393cb2f75 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestDisplayNameFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java index 3ecc984c5106..efdcfdc8d679 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,8 @@ package org.junit.jupiter.engine.extension; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; @@ -21,7 +22,6 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; -import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.Preconditions; /** @@ -41,7 +41,7 @@ public boolean supportsTestTemplate(ExtensionContext context) { public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { Method testMethod = context.getRequiredTestMethod(); String displayName = context.getDisplayName(); - RepeatedTest repeatedTest = AnnotationUtils.findAnnotation(testMethod, RepeatedTest.class).get(); + RepeatedTest repeatedTest = findAnnotation(testMethod, RepeatedTest.class).get(); int totalRepetitions = totalRepetitions(repeatedTest, testMethod); AtomicInteger failureCount = new AtomicInteger(); int failureThreshold = failureThreshold(repeatedTest, testMethod); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java index b40c3699e53c..b4f3f74e17b6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepeatedTestInvocationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java index e4975f866d52..b22d07419534 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/RepetitionExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -45,6 +45,11 @@ class RepetitionExtension implements ParameterResolver, TestWatcher, ExecutionCo this.repetitionInfo = repetitionInfo; } + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return (parameterContext.getParameter().getType() == RepetitionInfo.class); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java index fc4834eff4e9..7a5a78ab1544 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,8 @@ package org.junit.jupiter.engine.extension; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.function.Supplier; @@ -26,18 +28,20 @@ class SameThreadTimeoutInvocation implements Invocation { private final TimeoutDuration timeout; private final ScheduledExecutorService executor; private final Supplier descriptionSupplier; + private final PreInterruptCallbackInvocation preInterruptCallback; SameThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, ScheduledExecutorService executor, - Supplier descriptionSupplier) { + Supplier descriptionSupplier, PreInterruptCallbackInvocation preInterruptCallback) { this.delegate = delegate; this.timeout = timeout; this.executor = executor; this.descriptionSupplier = descriptionSupplier; + this.preInterruptCallback = preInterruptCallback; } @Override public T proceed() throws Throwable { - InterruptTask interruptTask = new InterruptTask(Thread.currentThread()); + InterruptTask interruptTask = new InterruptTask(Thread.currentThread(), preInterruptCallback); ScheduledFuture future = executor.schedule(interruptTask, timeout.getValue(), timeout.getUnit()); Throwable failure = null; T result = null; @@ -56,6 +60,7 @@ public T proceed() throws Throwable { if (interruptTask.executed) { Thread.interrupted(); failure = TimeoutExceptionFactory.create(descriptionSupplier.get(), timeout, failure); + interruptTask.attachSuppressedExceptions(failure); } } if (failure != null) { @@ -65,20 +70,28 @@ public T proceed() throws Throwable { } static class InterruptTask implements Runnable { - + private final PreInterruptCallbackInvocation preInterruptCallback; + private final List exceptionsDuringInterruption = new CopyOnWriteArrayList<>(); private final Thread thread; private volatile boolean executed; - InterruptTask(Thread thread) { + InterruptTask(Thread thread, PreInterruptCallbackInvocation preInterruptCallback) { this.thread = thread; + this.preInterruptCallback = preInterruptCallback; } @Override public void run() { executed = true; + preInterruptCallback.executePreInterruptCallback(thread, exceptionsDuringInterruption::add); thread.interrupt(); } + void attachSuppressedExceptions(Throwable outerException) { + for (Throwable throwable : exceptionsDuringInterruption) { + outerException.addSuppressed(throwable); + } + } } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java index f102bae0cf9a..5500f773720e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -25,19 +25,22 @@ class SeparateThreadTimeoutInvocation implements Invocation { private final Invocation delegate; private final TimeoutDuration timeout; private final Supplier descriptionSupplier; + private final PreInterruptCallbackInvocation preInterruptCallback; SeparateThreadTimeoutInvocation(Invocation delegate, TimeoutDuration timeout, - Supplier descriptionSupplier) { + Supplier descriptionSupplier, PreInterruptCallbackInvocation preInterruptCallback) { this.delegate = delegate; this.timeout = timeout; this.descriptionSupplier = descriptionSupplier; + this.preInterruptCallback = preInterruptCallback; } @Override public T proceed() throws Throwable { return assertTimeoutPreemptively(timeout.toDuration(), delegate::proceed, descriptionSupplier, - (__, messageSupplier, cause) -> { + (__, messageSupplier, cause, testThread) -> { TimeoutException exception = TimeoutExceptionFactory.create(messageSupplier.get(), timeout, null); + preInterruptCallback.executePreInterruptCallback(testThread, exception::addSuppressed); exception.initCause(cause); return exception; }); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java index f165b2e55968..eba68262c8e5 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,20 +12,24 @@ import static java.nio.file.FileVisitResult.CONTINUE; import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; import static org.junit.jupiter.api.io.CleanupMode.NEVER; import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; +import static org.junit.platform.commons.util.ReflectionUtils.isRecordObject; import java.io.File; import java.io.IOException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Parameter; import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileSystems; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -49,7 +53,6 @@ import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; @@ -57,40 +60,49 @@ import org.junit.jupiter.engine.config.EnumConfigurationParameterConverter; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ToStringBuilder; /** * {@code TempDirectory} is a JUnit Jupiter extension that creates and cleans - * up temporary directories if field in a test class or a parameter in a - * lifecycle method or test method is annotated with {@code @TempDir}. + * up temporary directories if a field in a test class or a parameter in a + * test class constructor, lifecycle method, or test method is annotated with + * {@code @TempDir}. * *

Consult the Javadoc for {@link TempDir} for details on the contract. * * @since 5.4 - * @see TempDir + * @see TempDir @TempDir * @see Files#createTempDirectory */ class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterResolver { + // package-private for testing purposes static final Namespace NAMESPACE = Namespace.create(TempDirectory.class); + static final String FILE_OPERATIONS_KEY = "file.operations"; + private static final String KEY = "temp.dir"; private static final String FAILURE_TRACKER = "failure.tracker"; private static final String CHILD_FAILED = "child.failed"; - // for testing purposes - static final String FILE_OPERATIONS_KEY = "file.operations"; - private final JupiterConfiguration configuration; public TempDirectory(JupiterConfiguration configuration) { this.configuration = configuration; } + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } + /** * Perform field injection for non-private, {@code static} fields (i.e., * class fields) of type {@link Path} or {@link File} that are annotated with @@ -116,19 +128,21 @@ public void beforeEach(ExtensionContext context) { private static void installFailureTracker(ExtensionContext context) { context.getStore(NAMESPACE).put(FAILURE_TRACKER, (CloseableResource) () -> context.getParent() // - .ifPresent(it -> { + .ifPresent(parentContext -> { if (selfOrChildFailed(context)) { - it.getStore(NAMESPACE).put(CHILD_FAILED, true); + parentContext.getStore(NAMESPACE).put(CHILD_FAILED, true); } })); } private void injectStaticFields(ExtensionContext context, Class testClass) { - injectFields(context, null, testClass, ReflectionUtils::isStatic); + injectFields(context, null, testClass, ModifierSupport::isStatic); } private void injectInstanceFields(ExtensionContext context, Object instance) { - injectFields(context, instance, instance.getClass(), ReflectionUtils::isNotStatic); + if (!isRecordObject(instance)) { + injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic); + } } private void injectFields(ExtensionContext context, Object testInstance, Class testClass, @@ -144,7 +158,7 @@ private void injectFields(ExtensionContext context, Object testInstance, Class type) { + private static void assertSupportedType(String target, Class type) { if (type != Path.class && type != File.class) { throw new ExtensionConfigurationException("Can only resolve @TempDir " + target + " of type " + Path.class.getName() + " or " + File.class.getName() + " but was: " + type.getName()); } } - private Object getPathOrFile(AnnotatedElementContext elementContext, Class type, TempDirFactory factory, - CleanupMode cleanupMode, Scope scope, ExtensionContext extensionContext) { + private static Object getPathOrFile(Class elementType, AnnotatedElementContext elementContext, + TempDirFactory factory, CleanupMode cleanupMode, Scope scope, ExtensionContext extensionContext) { + Namespace namespace = scope == Scope.PER_DECLARATION // ? NAMESPACE.append(elementContext) // : NAMESPACE; Path path = extensionContext.getStore(namespace) // - .getOrComputeIfAbsent(KEY, __ -> createTempDir(factory, cleanupMode, elementContext, extensionContext), + .getOrComputeIfAbsent(KEY, + __ -> createTempDir(factory, cleanupMode, elementType, elementContext, extensionContext), CloseablePath.class) // .get(); - return (type == Path.class) ? path : path.toFile(); + return (elementType == Path.class) ? path : path.toFile(); } - static CloseablePath createTempDir(TempDirFactory factory, CleanupMode cleanupMode, + static CloseablePath createTempDir(TempDirFactory factory, CleanupMode cleanupMode, Class elementType, AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + try { - return new CloseablePath(factory, cleanupMode, elementContext, extensionContext); + return new CloseablePath(factory, cleanupMode, elementType, elementContext, extensionContext); } catch (Exception ex) { throw new ExtensionConfigurationException("Failed to create default temp directory", ex); @@ -282,29 +294,45 @@ static class CloseablePath implements CloseableResource { private final Path dir; private final TempDirFactory factory; private final CleanupMode cleanupMode; + private final AnnotatedElement annotatedElement; private final ExtensionContext extensionContext; - CloseablePath(TempDirFactory factory, CleanupMode cleanupMode, AnnotatedElementContext elementContext, - ExtensionContext extensionContext) throws Exception { + private CloseablePath(TempDirFactory factory, CleanupMode cleanupMode, Class elementType, + AnnotatedElementContext elementContext, ExtensionContext extensionContext) throws Exception { this.dir = factory.createTempDirectory(elementContext, extensionContext); this.factory = factory; this.cleanupMode = cleanupMode; + this.annotatedElement = elementContext.getAnnotatedElement(); this.extensionContext = extensionContext; + + if (this.dir == null || !Files.isDirectory(this.dir)) { + close(); + throw new PreconditionViolationException("temp directory must be a directory"); + } + + if (elementType == File.class && !this.dir.getFileSystem().equals(FileSystems.getDefault())) { + close(); + throw new PreconditionViolationException( + "temp directory with non-default file system cannot be injected into " + File.class.getName() + + " target"); + } } Path get() { - return dir; + return this.dir; } @Override public void close() throws IOException { try { - if (cleanupMode == NEVER || (cleanupMode == ON_SUCCESS && selfOrChildFailed(extensionContext))) { - logger.info(() -> "Skipping cleanup of temp dir " + dir + " due to cleanup mode configuration."); + if (this.cleanupMode == NEVER + || (this.cleanupMode == ON_SUCCESS && selfOrChildFailed(this.extensionContext))) { + logger.info(() -> String.format("Skipping cleanup of temp dir %s for %s due to CleanupMode.%s.", + this.dir, descriptionFor(this.annotatedElement), this.cleanupMode.name())); return; } - FileOperations fileOperations = extensionContext.getStore(NAMESPACE) // + FileOperations fileOperations = this.extensionContext.getStore(NAMESPACE) // .getOrDefault(FILE_OPERATIONS_KEY, FileOperations.class, FileOperations.DEFAULT); SortedMap failures = deleteAllFilesAndDirectories(fileOperations); @@ -313,20 +341,48 @@ public void close() throws IOException { } } finally { - factory.close(); + this.factory.close(); } } + /** + * @since 5.12 + */ + private static String descriptionFor(AnnotatedElement annotatedElement) { + if (annotatedElement instanceof Field) { + Field field = (Field) annotatedElement; + return "field " + field.getDeclaringClass().getSimpleName() + "." + field.getName(); + } + if (annotatedElement instanceof Parameter) { + Parameter parameter = (Parameter) annotatedElement; + Executable executable = parameter.getDeclaringExecutable(); + return "parameter '" + parameter.getName() + "' in " + descriptionFor(executable); + } + throw new IllegalStateException("Unsupported AnnotatedElement type for @TempDir: " + annotatedElement); + } + + /** + * @since 5.12 + */ + private static String descriptionFor(Executable executable) { + boolean isConstructor = executable instanceof Constructor; + String type = isConstructor ? "constructor" : "method"; + String name = isConstructor ? executable.getDeclaringClass().getSimpleName() : executable.getName(); + return String.format("%s %s(%s)", type, name, + ClassUtils.nullSafeToString(Class::getSimpleName, executable.getParameterTypes())); + } + private SortedMap deleteAllFilesAndDirectories(FileOperations fileOperations) throws IOException { - if (Files.notExists(dir)) { + + if (this.dir == null || Files.notExists(this.dir)) { return Collections.emptySortedMap(); } SortedMap failures = new TreeMap<>(); Set retriedPaths = new HashSet<>(); - tryToResetPermissions(dir); - Files.walkFileTree(dir, new SimpleFileVisitor() { + tryToResetPermissions(this.dir); + Files.walkFileTree(this.dir, new SimpleFileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { @@ -431,7 +487,7 @@ private IOException createIOExceptionWithAttachedFailures(SortedMap emptyPath.equals(path) ? "" : path.toString()) // .collect(joining(", ")); - IOException exception = new IOException("Failed to delete temp directory " + dir.toAbsolutePath() + IOException exception = new IOException("Failed to delete temp directory " + this.dir.toAbsolutePath() + ". The following paths could not be deleted (see suppressed exceptions for details): " + joinedPaths); failures.values().forEach(exception::addSuppressed); @@ -449,7 +505,7 @@ private Path tryToDeleteOnExit(Path path) { private Path relativizeSafely(Path path) { try { - return dir.relativize(path); + return this.dir.relativize(path); } catch (IllegalArgumentException e) { return path; diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java index 6805daece4a1..ad6c7f8f2143 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestInfoParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -28,6 +28,11 @@ */ class TestInfoParameterResolver implements ParameterResolver { + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return (parameterContext.getParameter().getType() == TestInfo.class); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java index 57e402da37e8..7418341e2729 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TestReporterParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,6 +22,11 @@ */ class TestReporterParameterResolver implements ParameterResolver { + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { return (parameterContext.getParameter().getType() == TestReporter.class); @@ -29,7 +34,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public TestReporter resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return extensionContext::publishReportEntry; + return new DefaultTestReporter(extensionContext); } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java index 6238c0c4e1d7..60a8734db382 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java index 977b6ba8f383..ac99448af8a9 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDuration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java index 28f28e8ce72c..e5be5afefc3c 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutDurationParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java index b76269ac6d59..41174c609857 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java index f9b23e0e4cbd..f7fd783d077a 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -45,6 +45,11 @@ class TimeoutExtension implements BeforeAllCallback, BeforeEachCallback, Invocat private static final String DISABLED_MODE_VALUE = "disabled"; private static final String DISABLED_ON_DEBUG_MODE_VALUE = "disabled_on_debug"; + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public void beforeAll(ExtensionContext context) { readAndStoreTimeoutSoChildrenInheritIt(context); @@ -177,8 +182,8 @@ private Invocation decorate(Invocation invocation, ReflectiveInvocatio ThreadMode threadMode = resolveTimeoutThreadMode(extensionContext); return new TimeoutInvocationFactory(extensionContext.getRoot().getStore(NAMESPACE)).create(threadMode, - new TimeoutInvocationParameters<>(invocation, timeout, - () -> describe(invocationContext, extensionContext))); + new TimeoutInvocationParameters<>(invocation, timeout, () -> describe(invocationContext, extensionContext), + PreInterruptCallbackInvocationFactory.create((ExtensionContextInternal) extensionContext))); } private ThreadMode resolveTimeoutThreadMode(ExtensionContext extensionContext) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java index 004915069e32..5e9b04c18ef8 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -39,11 +39,13 @@ Invocation create(ThreadMode threadMode, TimeoutInvocationParameters t Preconditions.notNull(timeoutInvocationParameters, "timeout invocation parameters must not be null"); if (threadMode == ThreadMode.SEPARATE_THREAD) { return new SeparateThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), - timeoutInvocationParameters.getTimeoutDuration(), timeoutInvocationParameters.getDescriptionSupplier()); + timeoutInvocationParameters.getTimeoutDuration(), timeoutInvocationParameters.getDescriptionSupplier(), + timeoutInvocationParameters.getPreInterruptCallback()); } return new SameThreadTimeoutInvocation<>(timeoutInvocationParameters.getInvocation(), timeoutInvocationParameters.getTimeoutDuration(), getThreadExecutorForSameThreadInvocation(), - timeoutInvocationParameters.getDescriptionSupplier()); + timeoutInvocationParameters.getDescriptionSupplier(), + timeoutInvocationParameters.getPreInterruptCallback()); } private ScheduledExecutorService getThreadExecutorForSameThreadInvocation() { @@ -90,13 +92,16 @@ static class TimeoutInvocationParameters { private final Invocation invocation; private final TimeoutDuration timeout; private final Supplier descriptionSupplier; + private final PreInterruptCallbackInvocation preInterruptCallback; TimeoutInvocationParameters(Invocation invocation, TimeoutDuration timeout, - Supplier descriptionSupplier) { + Supplier descriptionSupplier, PreInterruptCallbackInvocation preInterruptCallback) { this.invocation = Preconditions.notNull(invocation, "invocation must not be null"); this.timeout = Preconditions.notNull(timeout, "timeout must not be null"); this.descriptionSupplier = Preconditions.notNull(descriptionSupplier, "description supplier must not be null"); + this.preInterruptCallback = Preconditions.notNull(preInterruptCallback, + "preInterruptCallback must not be null"); } public Invocation getInvocation() { @@ -110,5 +115,9 @@ public TimeoutDuration getTimeoutDuration() { public Supplier getDescriptionSupplier() { return descriptionSupplier; } + + public PreInterruptCallbackInvocation getPreInterruptCallback() { + return preInterruptCallback; + } } } diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java index bb06d12759a2..6c9eabb3a25e 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/JupiterThrowableCollectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java index bcaffa8f6503..11359f67b732 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,7 +15,7 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import org.opentest4j.TestAbortedException; @@ -49,7 +49,7 @@ private static Predicate createAbortedExecutionPredicate() { // Additionally support JUnit 4's AssumptionViolatedException? try { - Class clazz = ReflectionUtils.tryToLoadClass(ASSUMPTION_VIOLATED_EXCEPTION).get(); + Class clazz = ReflectionSupport.tryToLoadClass(ASSUMPTION_VIOLATED_EXCEPTION).get(); if (clazz != null) { return otaPredicate.or(clazz::isInstance); } diff --git a/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java b/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java index 946b1dd3c6fb..e8e188aad477 100644 --- a/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java +++ b/junit-jupiter-engine/src/module/org.junit.jupiter.engine/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 @@ -9,13 +9,13 @@ */ /** - * Provides the JUnit Jupiter {@linkplain org.junit.platform.engine.TestEngine} + * Provides the JUnit Jupiter {@link org.junit.platform.engine.TestEngine} * implementation. * * @since 5.0 * @uses org.junit.jupiter.api.extension.Extension * @provides org.junit.platform.engine.TestEngine The {@code JupiterTestEngine} - * runs Jupiter based tests on the platform. + * runs Jupiter based tests on the platform. */ module org.junit.jupiter.engine { requires static org.apiguardian.api; diff --git a/junit-jupiter-engine/src/nativeImage/initialize-at-build-time b/junit-jupiter-engine/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..05880451fb5a --- /dev/null +++ b/junit-jupiter-engine/src/nativeImage/initialize-at-build-time @@ -0,0 +1,22 @@ +org.junit.jupiter.engine.JupiterTestEngine +org.junit.jupiter.engine.config.CachingJupiterConfiguration +org.junit.jupiter.engine.config.DefaultJupiterConfiguration +org.junit.jupiter.engine.config.EnumConfigurationParameterConverter +org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter +org.junit.jupiter.engine.descriptor.ClassTestDescriptor +org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor +org.junit.jupiter.engine.descriptor.DynamicDescendantFilter +org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1 +org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor +org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$1 +org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor +org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor +org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor +org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor +org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor +org.junit.jupiter.engine.execution.ConditionEvaluator +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall +org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall +org.junit.jupiter.engine.execution.InvocationInterceptorChain diff --git a/junit-jupiter-engine/src/test/README.md b/junit-jupiter-engine/src/test/README.md new file mode 100644 index 000000000000..86ed9e49af1d --- /dev/null +++ b/junit-jupiter-engine/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java deleted file mode 100644 index 01036e36a5a7..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link DisabledForJreRange}. - * - *

Note that test method names MUST match the test method names in - * {@link DisabledForJreRangeIntegrationTests}. - * - * @since 5.6 - */ -class DisabledForJreRangeConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new DisabledForJreRangeCondition(); - } - - @Override - protected Class getTestClass() { - return DisabledForJreRangeIntegrationTests.class; - } - - /** - * @see DisabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@DisabledForJreRange is not present"); - } - - /** - * @see DisabledForJreRangeIntegrationTests#defaultValues() - */ - @Test - void defaultValues() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(this::evaluateCondition)// - .withMessageContaining("You must declare a non-default value for min or max in @DisabledForJreRange"); - } - - /** - * @see DisabledForJreRangeIntegrationTests#java17() - */ - @Test - void java17() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava17()); - } - - /** - * @see DisabledForJreRangeIntegrationTests#java18to19() - */ - @Test - void java18to19() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava18() || onJava19()); - assertCustomDisabledReasonIs("Disabled on some JRE"); - } - - /** - * @see DisabledForJreRangeIntegrationTests#javaMax18() - */ - @Test - void javaMax18() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() - || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); - } - - /** - * @see DisabledForJreRangeIntegrationTests#javaMin18() - */ - @Test - void javaMin18() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(!onJava17()); - } - - /** - * @see DisabledForJreRangeIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertDisabledOnCurrentJreIf(!onKnownVersion()); - } - - private void assertDisabledOnCurrentJreIf(boolean condition) { - if (condition) { - assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); - } - else { - assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); - } - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java deleted file mode 100644 index ff6ef050306b..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.OTHER; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link DisabledForJreRange}. - * - * @since 5.6 - */ -class DisabledForJreRangeIntegrationTests { - - @Test - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @DisabledForJreRange - void defaultValues() { - fail("should result in a configuration exception"); - } - - @Test - @DisabledForJreRange(min = JAVA_17, max = JAVA_17) - void java17() { - assertFalse(onJava17()); - } - - @Test - @DisabledForJreRange(min = JAVA_18, max = JAVA_19, disabledReason = "Disabled on some JRE") - void java18to19() { - assertFalse(onJava18() || onJava19()); - } - - @Test - @DisabledForJreRange(max = JAVA_18) - void javaMax18() { - assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18()); - } - - @Test - @DisabledForJreRange(min = JAVA_18) - void javaMin18() { - assertFalse(onJava18() || onJava19()); - assertTrue(onJava17()); - } - - @Test - @DisabledForJreRange(min = OTHER, max = OTHER) - void other() { - assertTrue(onKnownVersion()); - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java deleted file mode 100644 index 03b870b462ea..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExecutionCondition; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * Unit tests for {@link EnabledForJreRange}. - * - *

Note that test method names MUST match the test method names in - * {@link EnabledForJreRangeIntegrationTests}. - * - * @since 5.6 - */ -class EnabledForJreRangeConditionTests extends AbstractExecutionConditionTests { - - @Override - protected ExecutionCondition getExecutionCondition() { - return new EnabledForJreRangeCondition(); - } - - @Override - protected Class getTestClass() { - return EnabledForJreRangeIntegrationTests.class; - } - - /** - * @see EnabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() - */ - @Test - void enabledBecauseAnnotationIsNotPresent() { - evaluateCondition(); - assertEnabled(); - assertReasonContains("@EnabledForJreRange is not present"); - } - - /** - * @see EnabledForJreRangeIntegrationTests#defaultValues() - */ - @Test - void defaultValues() { - assertThatExceptionOfType(PreconditionViolationException.class)// - .isThrownBy(this::evaluateCondition)// - .withMessageContaining("You must declare a non-default value for min or max in @EnabledForJreRange"); - } - - /** - * @see EnabledForJreRangeIntegrationTests#java17() - */ - @Test - void java17() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava17()); - } - - /** - * @see EnabledForJreRangeIntegrationTests#java18to19() - */ - @Test - void java18to19() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava18() || onJava19()); - } - - /** - * @see EnabledForJreRangeIntegrationTests#javaMax18() - */ - @Test - void javaMax18() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() - || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); - } - - /** - * @see EnabledForJreRangeIntegrationTests#javaMin18() - */ - @Test - void javaMin18() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(!(onJava17())); - } - - /** - * @see EnabledForJreRangeIntegrationTests#other() - */ - @Test - void other() { - evaluateCondition(); - assertEnabledOnCurrentJreIf(!onKnownVersion()); - } - - private void assertEnabledOnCurrentJreIf(boolean condition) { - if (condition) { - assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); - } - else { - assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); - } - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java deleted file mode 100644 index 074b26f01da5..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.OTHER; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; -import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -/** - * Integration tests for {@link EnabledForJreRange}. - * - * @since 5.6 - */ -class EnabledForJreRangeIntegrationTests { - - @Test - @Disabled("Only used in a unit test via reflection") - void enabledBecauseAnnotationIsNotPresent() { - } - - @Test - @Disabled("Only used in a unit test via reflection") - @EnabledForJreRange - void defaultValues() { - fail("should result in a configuration exception"); - } - - @Test - @EnabledForJreRange(min = JAVA_17, max = JAVA_17) - void java17() { - assertTrue(onJava17()); - } - - @Test - @EnabledForJreRange(min = JAVA_18, max = JAVA_19) - void java18to19() { - assertTrue(onJava18() || onJava19()); - assertFalse(onJava17()); - } - - @Test - @EnabledForJreRange(max = JAVA_18) - void javaMax18() { - assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() - || onJava15() || onJava16() || onJava17() || onJava18()); - assertFalse(onJava19()); - } - - @Test - @EnabledForJreRange(min = JAVA_18) - void javaMin18() { - assertTrue(onKnownVersion()); - assertTrue(JRE.currentVersion().compareTo(JAVA_18) >= 0); - assertFalse(onJava17()); - } - - @Test - @EnabledForJreRange(min = OTHER, max = OTHER) - void other() { - assertFalse(onKnownVersion()); - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java deleted file mode 100644 index 26e39ac49af6..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/JRETests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.api.condition; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.condition.JRE.JAVA_17; -import static org.junit.jupiter.api.condition.JRE.JAVA_18; -import static org.junit.jupiter.api.condition.JRE.JAVA_19; -import static org.junit.jupiter.api.condition.JRE.JAVA_20; -import static org.junit.jupiter.api.condition.JRE.JAVA_21; -import static org.junit.jupiter.api.condition.JRE.JAVA_22; -import static org.junit.jupiter.api.condition.JRE.OTHER; - -import org.junit.jupiter.api.Test; - -/** - * Unit tests for {@link JRE} - * - * @since 5.7 - */ -public class JRETests { - - @Test - @EnabledOnJre(JAVA_17) - void java17() { - assertEquals(JAVA_17, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_18) - void java18() { - assertEquals(JAVA_18, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_19) - void java19() { - assertEquals(JAVA_19, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_20) - void java20() { - assertEquals(JAVA_20, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_21) - void java21() { - assertEquals(JAVA_21, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(JAVA_22) - void java22() { - assertEquals(JAVA_22, JRE.currentVersion()); - } - - @Test - @EnabledOnJre(OTHER) - void other() { - assertEquals(OTHER, JRE.currentVersion()); - } -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java deleted file mode 100644 index 0e19f0466f16..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ReportingTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine; - -import static java.util.Collections.emptyMap; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestReporter; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class ReportingTests extends AbstractJupiterTestEngineTests { - - @Test - void reportEntriesArePublished() { - executeTestsForClass(MyReportingTestCase.class).testEvents().assertStatistics(stats -> stats // - .started(2) // - .succeeded(2) // - .failed(0) // - .reportingEntryPublished(7)); - } - - static class MyReportingTestCase { - - @BeforeEach - void beforeEach(TestReporter reporter) { - reporter.publishEntry("@BeforeEach"); - } - - @AfterEach - void afterEach(TestReporter reporter) { - reporter.publishEntry("@AfterEach"); - } - - @Test - void succeedingTest(TestReporter reporter) { - reporter.publishEntry(emptyMap()); - reporter.publishEntry("user name", "dk38"); - reporter.publishEntry("message"); - } - - @Test - void invalidReportData(TestReporter reporter) { - - // Maps - Map map = new HashMap<>(); - - map.put("key", null); - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); - - map.clear(); - map.put(null, "value"); - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); - - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((Map) null)); - - // Key-Value pair - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(null, "bar")); - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry("foo", null)); - - // Value - assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((String) null)); - } - - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java deleted file mode 100644 index 8badcd29d513..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.descriptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.parallel.ExecutionMode; -import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; -import org.junit.jupiter.engine.config.JupiterConfiguration; -import org.junit.jupiter.engine.execution.DefaultTestInstances; -import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.engine.ConfigurationParameters; -import org.junit.platform.engine.EngineExecutionListener; -import org.junit.platform.engine.TestDescriptor; -import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; -import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; - -/** - * Unit tests for concrete implementations of {@link ExtensionContext}: - * {@link JupiterEngineExtensionContext}, {@link ClassExtensionContext}, and - * {@link MethodExtensionContext}. - * - * @since 5.0 - * @see org.junit.jupiter.engine.execution.ExtensionValuesStoreTests - */ -public class ExtensionContextTests { - - private final JupiterConfiguration configuration = mock(); - - @BeforeEach - void setUp() { - when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); - when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); - when(configuration.getDefaultClassesExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); - } - - @Test - void fromJupiterEngineDescriptor() { - JupiterEngineDescriptor engineTestDescriptor = new JupiterEngineDescriptor( - UniqueId.root("engine", "junit-jupiter"), configuration); - - JupiterEngineExtensionContext engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, - configuration, null); - - // @formatter:off - assertAll("engineContext", - () -> assertThat(engineContext.getElement()).isEmpty(), - () -> assertThat(engineContext.getTestClass()).isEmpty(), - () -> assertThat(engineContext.getTestInstance()).isEmpty(), - () -> assertThat(engineContext.getTestMethod()).isEmpty(), - () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestClass()), - () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestInstance()), - () -> assertThrows(PreconditionViolationException.class, () -> engineContext.getRequiredTestMethod()), - () -> assertThat(engineContext.getDisplayName()).isEqualTo(engineTestDescriptor.getDisplayName()), - () -> assertThat(engineContext.getParent()).isEmpty(), - () -> assertThat(engineContext.getRoot()).isSameAs(engineContext), - () -> assertThat(engineContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) - ); - // @formatter:on - } - - @Test - @SuppressWarnings("resource") - void fromClassTestDescriptor() { - NestedClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor(); - ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); - - ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, - configuration, null, null); - - // @formatter:off - assertAll("outerContext", - () -> assertThat(outerExtensionContext.getElement()).contains(OuterClass.class), - () -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClass.class), - () -> assertThat(outerExtensionContext.getTestInstance()).isEmpty(), - () -> assertThat(outerExtensionContext.getTestMethod()).isEmpty(), - () -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), - () -> assertThrows(PreconditionViolationException.class, () -> outerExtensionContext.getRequiredTestInstance()), - () -> assertThrows(PreconditionViolationException.class, () -> outerExtensionContext.getRequiredTestMethod()), - () -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()), - () -> assertThat(outerExtensionContext.getParent()).isEmpty(), - () -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) - ); - // @formatter:on - - ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, - nestedClassDescriptor, configuration, null, null); - assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext); - } - - @Test - @SuppressWarnings("resource") - void tagsCanBeRetrievedInExtensionContext() { - NestedClassTestDescriptor nestedClassDescriptor = nestedClassDescriptor(); - ClassTestDescriptor outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); - TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); - outerClassDescriptor.addChild(methodTestDescriptor); - - ClassExtensionContext outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, - configuration, null, null); - - assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag"); - assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext); - - ClassExtensionContext nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, - nestedClassDescriptor, configuration, null, null); - assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag"); - assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext); - - MethodExtensionContext methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, - methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null); - methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); - assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag"); - assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext); - } - - @Test - @SuppressWarnings("resource") - void fromMethodTestDescriptor() { - TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); - ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor); - JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(UniqueId.forEngine("junit-jupiter"), - configuration); - engineDescriptor.addChild(classTestDescriptor); - - Object testInstance = new OuterClass(); - Method testMethod = methodTestDescriptor.getTestMethod(); - - JupiterEngineExtensionContext engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, - configuration, null); - ClassExtensionContext classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, - classTestDescriptor, configuration, null, null); - MethodExtensionContext methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, - methodTestDescriptor, configuration, new OpenTest4JAwareThrowableCollector(), null); - methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance)); - - // @formatter:off - assertAll("methodContext", - () -> assertThat(methodExtensionContext.getElement()).contains(testMethod), - () -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClass.class), - () -> assertThat(methodExtensionContext.getTestInstance()).contains(testInstance), - () -> assertThat(methodExtensionContext.getTestMethod()).contains(testMethod), - () -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), - () -> assertThat(methodExtensionContext.getRequiredTestInstance()).isEqualTo(testInstance), - () -> assertThat(methodExtensionContext.getRequiredTestMethod()).isEqualTo(testMethod), - () -> assertThat(methodExtensionContext.getDisplayName()).isEqualTo(methodTestDescriptor.getDisplayName()), - () -> assertThat(methodExtensionContext.getParent()).contains(classExtensionContext), - () -> assertThat(methodExtensionContext.getRoot()).isSameAs(engineExtensionContext), - () -> assertThat(methodExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) - ); - // @formatter:on - } - - @Test - @SuppressWarnings("resource") - void reportEntriesArePublishedToExecutionContext() { - ClassTestDescriptor classTestDescriptor = outerClassDescriptor(null); - EngineExecutionListener engineExecutionListener = Mockito.spy(EngineExecutionListener.class); - ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener, - classTestDescriptor, configuration, null, null); - - Map map1 = Collections.singletonMap("key", "value"); - Map map2 = Collections.singletonMap("other key", "other value"); - - extensionContext.publishReportEntry(map1); - extensionContext.publishReportEntry(map2); - extensionContext.publishReportEntry("3rd key", "third value"); - extensionContext.publishReportEntry("status message"); - - ArgumentCaptor entryCaptor = ArgumentCaptor.forClass(ReportEntry.class); - Mockito.verify(engineExecutionListener, Mockito.times(4)).reportingEntryPublished( - ArgumentMatchers.eq(classTestDescriptor), entryCaptor.capture()); - - ReportEntry reportEntry1 = entryCaptor.getAllValues().get(0); - ReportEntry reportEntry2 = entryCaptor.getAllValues().get(1); - ReportEntry reportEntry3 = entryCaptor.getAllValues().get(2); - ReportEntry reportEntry4 = entryCaptor.getAllValues().get(3); - - assertEquals(map1, reportEntry1.getKeyValuePairs()); - assertEquals(map2, reportEntry2.getKeyValuePairs()); - assertEquals("third value", reportEntry3.getKeyValuePairs().get("3rd key")); - assertEquals("status message", reportEntry4.getKeyValuePairs().get("value")); - } - - @Test - @SuppressWarnings("resource") - void usingStore() { - TestMethodTestDescriptor methodTestDescriptor = methodDescriptor(); - ClassTestDescriptor classTestDescriptor = outerClassDescriptor(methodTestDescriptor); - ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, configuration, null, - null); - MethodExtensionContext childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, - configuration, new OpenTest4JAwareThrowableCollector(), null); - childContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); - - ExtensionContext.Store childStore = childContext.getStore(Namespace.GLOBAL); - ExtensionContext.Store parentStore = parentContext.getStore(Namespace.GLOBAL); - - final Object key1 = "key 1"; - final String value1 = "a value"; - childStore.put(key1, value1); - assertEquals(value1, childStore.get(key1)); - assertEquals(value1, childStore.remove(key1)); - assertNull(childStore.get(key1)); - - childStore.put(key1, value1); - assertEquals(value1, childStore.get(key1)); - assertEquals(value1, childStore.remove(key1, String.class)); - assertNull(childStore.get(key1)); - - final Object key2 = "key 2"; - final String value2 = "other value"; - assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2)); - assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2, String.class)); - assertEquals(value2, childStore.get(key2)); - assertEquals(value2, childStore.get(key2, String.class)); - - final Object parentKey = "parent key"; - final String parentValue = "parent value"; - parentStore.put(parentKey, parentValue); - assertEquals(parentValue, childStore.getOrComputeIfAbsent(parentKey, k -> "a different value")); - assertEquals(parentValue, childStore.get(parentKey)); - } - - @TestFactory - Stream configurationParameter() throws Exception { - JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters()); - String key = "123"; - Optional expected = Optional.of(key); - - UniqueId engineUniqueId = UniqueId.parse("[engine:junit-jupiter]"); - JupiterEngineDescriptor engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration); - - UniqueId classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); - ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor(classUniqueId, getClass(), configuration); - - Method method = getClass().getDeclaredMethod("configurationParameter"); - UniqueId methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); - TestMethodTestDescriptor methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, getClass(), method, - configuration); - - return Stream.of( // - (ExtensionContext) new JupiterEngineExtensionContext(null, engineDescriptor, echo, null), // - new ClassExtensionContext(null, null, classTestDescriptor, echo, null, null), // - new MethodExtensionContext(null, null, methodTestDescriptor, echo, null, null) // - ).map(context -> dynamicTest(context.getClass().getSimpleName(), - () -> assertEquals(expected, context.getConfigurationParameter(key)))); - } - - private NestedClassTestDescriptor nestedClassDescriptor() { - return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClass.NestedClass.class, - configuration); - } - - private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) { - ClassTestDescriptor classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), - OuterClass.class, configuration); - if (child != null) { - classTestDescriptor.addChild(child); - } - return classTestDescriptor; - } - - private TestMethodTestDescriptor methodDescriptor() { - try { - return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClass.class, - OuterClass.class.getDeclaredMethod("aMethod"), configuration); - } - catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - @Tag("outer-tag") - static class OuterClass { - - @Tag("nested-tag") - class NestedClass { - } - - @Tag("method-tag") - void aMethod() { - } - } - - private static class EchoParameters implements ConfigurationParameters { - - @Override - public Optional get(String key) { - return Optional.of(key); - } - - @Override - public Optional getBoolean(String key) { - throw new UnsupportedOperationException("getBoolean(String) should not be called"); - } - - @Override - @SuppressWarnings("deprecation") - public int size() { - throw new UnsupportedOperationException("size() should not be called"); - } - - @Override - public Set keySet() { - throw new UnsupportedOperationException("keySet() should not be called"); - - } - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java deleted file mode 100644 index 2ed3e370b58a..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/CloseablePathCleanupTests.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static java.nio.file.Files.deleteIfExists; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; -import static org.junit.jupiter.api.io.CleanupMode.NEVER; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.util.Optional; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.AnnotatedElementContext; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.io.TempDir; -import org.junit.jupiter.api.io.TempDirFactory; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.jupiter.engine.execution.NamespaceAwareStore; -import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; - -/** - * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is - * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. - * - * @since 5.9 - * - * @see TempDir - * @see CleanupMode - */ -class CloseablePathCleanupTests extends AbstractJupiterTestEngineTests { - - private final AnnotatedElementContext elementContext = mock(); - private final ExtensionContext extensionContext = mock(); - private final TempDirFactory factory = spy(TempDirFactory.Standard.INSTANCE); - - private TempDirectory.CloseablePath closeablePath; - - @BeforeEach - void setUpExtensionContext() { - var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL); - when(extensionContext.getStore(any())).thenReturn(store); - } - - @AfterEach - void cleanupTempDirectory() throws IOException { - deleteIfExists(closeablePath.get()); - } - - @Test - @DisplayName("is cleaned up for a cleanup mode of ALWAYS") - void always() throws IOException { - closeablePath = TempDirectory.createTempDir(factory, ALWAYS, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).doesNotExist(); - verify(factory).close(); - } - - @Test - @DisplayName("is not cleaned up for a cleanup mode of NEVER") - void never() throws IOException { - closeablePath = TempDirectory.createTempDir(factory, NEVER, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).exists(); - verify(factory).close(); - } - - @Test - @DisplayName("is not cleaned up for a cleanup mode of ON_SUCCESS, if there is an exception") - void onSuccessWithException() throws IOException { - when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); - - closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).exists(); - verify(factory).close(); - } - - @Test - @DisplayName("is cleaned up for a cleanup mode of ON_SUCCESS, if there is no exception") - void onSuccessWithNoException() throws IOException { - when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); - - closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementContext, extensionContext); - assertThat(closeablePath.get()).exists(); - - closeablePath.close(); - assertThat(closeablePath.get()).doesNotExist(); - verify(factory).close(); - } - -} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java deleted file mode 100644 index a9b4fe75cf03..000000000000 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package org.junit.jupiter.engine.extension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestInstancePostProcessor; -import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; - -/** - * Integration tests that verify support for {@link TestInstancePostProcessor}. - * - * @since 5.0 - */ -class TestInstancePostProcessorTests extends AbstractJupiterTestEngineTests { - - private static final List callSequence = new ArrayList<>(); - - @BeforeEach - void resetCallSequence() { - callSequence.clear(); - } - - @Test - void instancePostProcessorsInNestedClasses() { - executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - - // @formatter:off - assertThat(callSequence).containsExactly( - - // OuterTestCase - "fooPostProcessTestInstance:OuterTestCase", - "beforeOuterMethod", - "testOuter", - - // InnerTestCase - - "fooPostProcessTestInstance:OuterTestCase", - "fooPostProcessTestInstance:InnerTestCase", - "barPostProcessTestInstance:InnerTestCase", - "beforeOuterMethod", - "beforeInnerMethod", - "testInner" - ); - // @formatter:on - } - - @Test - void testSpecificTestInstancePostProcessorIsCalled() { - executeTestsForClass(TestCaseWithTestSpecificTestInstancePostProcessor.class).testEvents()// - .assertStatistics(stats -> stats.started(1).succeeded(1)); - - assertThat(callSequence).containsExactly( - "fooPostProcessTestInstance:TestCaseWithTestSpecificTestInstancePostProcessor", "beforeEachMethod", "test"); - } - - // ------------------------------------------------------------------- - - @ExtendWith(FooInstancePostProcessor.class) - static class OuterTestCase implements Named { - - private String outerName; - - @Override - public void setName(String name) { - this.outerName = name; - } - - @BeforeEach - void beforeOuterMethod() { - callSequence.add("beforeOuterMethod"); - } - - @Test - void testOuter() { - assertEquals("foo:" + OuterTestCase.class.getSimpleName(), outerName); - callSequence.add("testOuter"); - } - - @Nested - @ExtendWith(BarInstancePostProcessor.class) - class InnerTestCase implements Named { - - private String innerName; - - @Override - public void setName(String name) { - this.innerName = name; - } - - @BeforeEach - void beforeInnerMethod() { - callSequence.add("beforeInnerMethod"); - } - - @Test - void testInner() { - assertEquals("foo:" + OuterTestCase.class.getSimpleName(), outerName); - assertEquals("bar:" + InnerTestCase.class.getSimpleName(), innerName); - callSequence.add("testInner"); - } - } - - } - - static class TestCaseWithTestSpecificTestInstancePostProcessor implements Named { - - private String name; - - @Override - public void setName(String name) { - this.name = name; - } - - @BeforeEach - void beforeEachMethod() { - callSequence.add("beforeEachMethod"); - } - - @ExtendWith(FooInstancePostProcessor.class) - @Test - void test() { - callSequence.add("test"); - assertEquals("foo:" + getClass().getSimpleName(), name); - } - } - - static class FooInstancePostProcessor implements TestInstancePostProcessor { - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - if (testInstance instanceof Named) { - ((Named) testInstance).setName("foo:" + context.getRequiredTestClass().getSimpleName()); - } - callSequence.add("fooPostProcessTestInstance:" + testInstance.getClass().getSimpleName()); - } - } - - static class BarInstancePostProcessor implements TestInstancePostProcessor { - - @Override - public void postProcessTestInstance(Object testInstance, ExtensionContext context) { - if (testInstance instanceof Named) { - ((Named) testInstance).setName("bar:" + context.getRequiredTestClass().getSimpleName()); - } - callSequence.add("barPostProcessTestInstance:" + testInstance.getClass().getSimpleName()); - } - } - - private interface Named { - - void setName(String name); - } - -} diff --git a/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java b/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java index 8f2699706b80..063d0527103f 100644 --- a/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java +++ b/junit-jupiter-engine/src/testFixtures/java/org/junit/jupiter/engine/discovery/JupiterUniqueIdBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts index d3779d057e4f..0f28afd9ea94 100644 --- a/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts +++ b/junit-jupiter-migrationsupport/junit-jupiter-migrationsupport.gradle.kts @@ -1,7 +1,6 @@ plugins { id("junitbuild.java-library-conventions") id("junitbuild.junit4-compatibility") - id("junitbuild.testing-conventions") } description = "JUnit Jupiter Migration Support" @@ -13,11 +12,6 @@ dependencies { compileOnlyApi(libs.apiguardian) - testImplementation(projects.junitJupiterEngine) - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(projects.junitPlatformTestkit) - osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) } diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java index ed34832b0fbd..0c9c04778493 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/EnableJUnit4MigrationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java index c1fa55f955e9..59ab33098a59 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/conditions/IgnoreCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.jupiter.migrationsupport.conditions; import static org.apiguardian.api.API.Status.STABLE; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.reflect.AnnotatedElement; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java index 4bd82f1d6488..243874b8265e 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java index 3b15203a9ffe..adecfda66993 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java index 8d9bae138aa6..347317733d4e 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java index 90026b7a1bd4..f424126d6fdd 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/TestRuleSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,9 +11,10 @@ package org.junit.jupiter.migrationsupport.rules; import static java.util.Collections.unmodifiableList; -import static org.junit.platform.commons.util.AnnotationUtils.findPublicAnnotatedFields; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.support.AnnotationSupport.findPublicAnnotatedFields; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; +import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -81,7 +82,7 @@ private List findAnnotatedMethods(Object testInstance) { Predicate isRuleMethod = method -> isAnnotated(method, Rule.class); Predicate hasCorrectReturnType = method -> TestRule.class.isAssignableFrom(method.getReturnType()); - return findMethods(testInstance.getClass(), isRuleMethod.and(hasCorrectReturnType)); + return findMethods(testInstance.getClass(), isRuleMethod.and(hasCorrectReturnType), TOP_DOWN); } private List findAnnotatedFields(Object testInstance) { diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java index 420e817061c9..a5025818722d 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/VerifierSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java index 219c24a90b55..5c60dcf5df24 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/AbstractTestRuleAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,8 +11,8 @@ package org.junit.jupiter.migrationsupport.rules.adapter; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; -import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; +import static org.junit.platform.commons.support.ReflectionSupport.findMethod; +import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; import java.lang.reflect.Method; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java index 15c114b9dc39..1d8b52a427d1 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExpectedExceptionAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java index f35a5de25d2b..8891f75da60c 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/ExternalResourceAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java index 329086883026..6b6b885f905e 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/GenericBeforeAndAfterAdvice.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java index 986e6894a4f7..b04de522b9a4 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/adapter/VerifierAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java index f93dc00a97a1..be6ea055b87d 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/AbstractTestRuleAnnotatedMember.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java index ef277f590fcd..5c68934d3ac4 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedField.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.jupiter.migrationsupport.rules.member; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; +import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import java.lang.reflect.Field; diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java index 48bed90147d8..0b959f53bdaf 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMember.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java index 13042a928da6..66a327370850 100644 --- a/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java +++ b/junit-jupiter-migrationsupport/src/main/java/org/junit/jupiter/migrationsupport/rules/member/TestRuleAnnotatedMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,7 +15,7 @@ import java.lang.reflect.Method; import org.apiguardian.api.API; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.rules.TestRule; /** @@ -25,7 +25,7 @@ public final class TestRuleAnnotatedMethod extends AbstractTestRuleAnnotatedMember { public TestRuleAnnotatedMethod(Object testInstance, Method method) { - super((TestRule) ReflectionUtils.invokeMethod(method, testInstance)); + super((TestRule) ReflectionSupport.invokeMethod(method, testInstance)); } } diff --git a/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java b/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java index 3ace009eb0ab..ced462bec0db 100644 --- a/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java +++ b/junit-jupiter-migrationsupport/src/module/org.junit.jupiter.migrationsupport/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/README.md b/junit-jupiter-migrationsupport/src/test/README.md new file mode 100644 index 000000000000..86ed9e49af1d --- /dev/null +++ b/junit-jupiter-migrationsupport/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. diff --git a/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml b/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml deleted file mode 100644 index 1c1cbb8d6acd..000000000000 --- a/junit-jupiter-migrationsupport/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/junit-jupiter-params/junit-jupiter-params.gradle.kts b/junit-jupiter-params/junit-jupiter-params.gradle.kts index ba16364bd682..e481fdd13674 100644 --- a/junit-jupiter-params/junit-jupiter-params.gradle.kts +++ b/junit-jupiter-params/junit-jupiter-params.gradle.kts @@ -1,8 +1,8 @@ plugins { id("junitbuild.kotlin-library-conventions") id("junitbuild.shadow-conventions") - id("junitbuild.testing-conventions") id("junitbuild.jmh-conventions") + id("junitbuild.native-image-properties") } description = "JUnit Jupiter Params" @@ -15,15 +15,7 @@ dependencies { shadowed(libs.univocity.parsers) - testImplementation(projects.junitPlatformTestkit) - testImplementation(projects.junitJupiterEngine) - testImplementation(projects.junitPlatformLauncher) - testImplementation(projects.junitPlatformSuiteEngine) - testImplementation(testFixtures(projects.junitPlatformCommons)) - testImplementation(testFixtures(projects.junitJupiterEngine)) - compileOnly(kotlin("stdlib")) - testImplementation(kotlin("stdlib")) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) diff --git a/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java b/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java index bc296054ec36..dad2ce3e2115 100644 --- a/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java +++ b/junit-jupiter-params/src/jmh/java/org/junit/jupiter/params/ParameterizedTestNameFormatterBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -44,18 +44,21 @@ public void setUp() { @Benchmark public void formatTestNames(Blackhole blackhole) throws Exception { + var method = TestCase.class.getDeclaredMethod("parameterizedTest", int.class); var formatter = new ParameterizedTestNameFormatter( ParameterizedTest.DISPLAY_NAME_PLACEHOLDER + " " + ParameterizedTest.DEFAULT_DISPLAY_NAME + " ({0})", - "displayName", - new ParameterizedTestMethodContext(TestCase.class.getDeclaredMethod("parameterizedTest", int.class)), 512); + "displayName", new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class)), + 512); for (int i = 0; i < argumentsList.size(); i++) { Arguments arguments = argumentsList.get(i); blackhole.consume(formatter.format(i, arguments, arguments.get())); } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @SuppressWarnings("unused") + @ParameterizedTest void parameterizedTest(int param) { } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidationMode.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidationMode.java new file mode 100644 index 000000000000..fe50db276cdf --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidationMode.java @@ -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.apiguardian.api.API; +import org.junit.jupiter.params.provider.ArgumentsSource; + +/** + * Enumeration of argument count validation modes for {@link ParameterizedTest @ParameterizedTest}. + * + *

When an {@link ArgumentsSource} provides more arguments than declared by the test method, + * there might be a bug in the test method or the {@link ArgumentsSource}. + * By default, the additional arguments are ignored. + * {@link ArgumentCountValidationMode} allows you to control how additional arguments are handled. + * + * @since 5.12 + * @see ParameterizedTest + */ +@API(status = API.Status.EXPERIMENTAL, since = "5.12") +public enum ArgumentCountValidationMode { + /** + * Use the default validation mode. + * + *

The default validation mode may be changed via the + * {@value ArgumentCountValidator#ARGUMENT_COUNT_VALIDATION_KEY} configuration parameter + * (see the User Guide for details on configuration parameters). + */ + DEFAULT, + + /** + * Use the "none" argument count validation mode. + * + *

When there are more arguments provided than declared by the test method, + * these additional arguments are ignored. + */ + NONE, + + /** + * Use the strict argument count validation mode. + * + *

When there are more arguments provided than declared by the test method, this raises an error. + */ + STRICT, +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java new file mode 100644 index 000000000000..42a1f48ea54c --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java @@ -0,0 +1,111 @@ +/* + * 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 java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.Preconditions; + +class ArgumentCountValidator implements InvocationInterceptor { + private static final Logger logger = LoggerFactory.getLogger(ArgumentCountValidator.class); + + static final String ARGUMENT_COUNT_VALIDATION_KEY = "junit.jupiter.params.argumentCountValidation"; + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( + ArgumentCountValidator.class); + + private final ParameterizedTestMethodContext methodContext; + private final Arguments arguments; + + ArgumentCountValidator(ParameterizedTestMethodContext methodContext, Arguments arguments) { + this.methodContext = methodContext; + this.arguments = arguments; + } + + @Override + public void interceptTestTemplateMethod(InvocationInterceptor.Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + validateArgumentCount(extensionContext, arguments); + invocation.proceed(); + } + + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getRoot().getStore(NAMESPACE); + } + + private void validateArgumentCount(ExtensionContext extensionContext, Arguments arguments) { + ArgumentCountValidationMode argumentCountValidationMode = getArgumentCountValidationMode(extensionContext); + switch (argumentCountValidationMode) { + case DEFAULT: + case NONE: + return; + case STRICT: + int testParamCount = extensionContext.getRequiredTestMethod().getParameterCount(); + int argumentsCount = arguments.get().length; + Preconditions.condition(testParamCount == argumentsCount, () -> String.format( + "Configuration error: the @ParameterizedTest has %s argument(s) but there were %s argument(s) provided.%nNote: the provided arguments are %s", + testParamCount, argumentsCount, Arrays.toString(arguments.get()))); + break; + default: + throw new ExtensionConfigurationException( + "Unsupported argument count validation mode: " + argumentCountValidationMode); + } + } + + private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionContext extensionContext) { + ParameterizedTest parameterizedTest = methodContext.annotation; + if (parameterizedTest.argumentCountValidation() != ArgumentCountValidationMode.DEFAULT) { + return parameterizedTest.argumentCountValidation(); + } + else { + return getArgumentCountValidationModeConfiguration(extensionContext); + } + } + + private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration(ExtensionContext extensionContext) { + String key = ARGUMENT_COUNT_VALIDATION_KEY; + ArgumentCountValidationMode fallback = ArgumentCountValidationMode.NONE; + ExtensionContext.Store store = getStore(extensionContext); + return store.getOrComputeIfAbsent(key, __ -> { + Optional optionalConfigValue = extensionContext.getConfigurationParameter(key); + if (optionalConfigValue.isPresent()) { + String configValue = optionalConfigValue.get(); + Optional enumValue = Arrays.stream( + ArgumentCountValidationMode.values()).filter( + mode -> mode.name().equalsIgnoreCase(configValue)).findFirst(); + if (enumValue.isPresent()) { + logger.config(() -> String.format( + "Using ArgumentCountValidationMode '%s' set via the '%s' configuration parameter.", + enumValue.get().name(), key)); + return enumValue.get(); + } + else { + logger.warn(() -> String.format( + "Invalid ArgumentCountValidationMode '%s' set via the '%s' configuration parameter. " + + "Falling back to the %s default value.", + configValue, key, fallback.name())); + return fallback; + } + } + else { + return fallback; + } + }, ArgumentCountValidationMode.class); + } +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java index bf45c87fd71b..ce16337b45d2 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,6 +22,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.provider.ArgumentsSource; /** * {@code @ParameterizedTest} is used to signal that the annotated method is a @@ -291,4 +292,35 @@ @API(status = STABLE, since = "5.10") boolean autoCloseArguments() default true; + /** + * Configure whether zero invocations are allowed for this + * parameterized test. + * + *

Set this attribute to {@code true} if the absence of invocations is + * expected in some cases and should not cause a test failure. + * + *

Defaults to {@code false}. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + boolean allowZeroInvocations() default false; + + /** + * Configure how the number of arguments provided by an {@link ArgumentsSource} are validated. + * + *

Defaults to {@link ArgumentCountValidationMode#DEFAULT}. + * + *

When an {@link ArgumentsSource} provides more arguments than declared by the test method, + * there might be a bug in the test method or the {@link ArgumentsSource}. + * By default, the additional arguments are ignored. + * {@code argumentCountValidation} allows you to control how additional arguments are handled. + * The default can be configured via the {@value ArgumentCountValidator#ARGUMENT_COUNT_VALIDATION_KEY} + * configuration parameter (see the User Guide for details on configuration parameters). + * + * @since 5.12 + * @see ArgumentCountValidationMode + */ + @API(status = EXPERIMENTAL, since = "5.12") + ArgumentCountValidationMode argumentCountValidation() default ArgumentCountValidationMode.DEFAULT; } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java index aa7cd4251720..9390c1fd3827 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,11 +10,12 @@ package org.junit.jupiter.params; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; @@ -26,17 +27,15 @@ import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.support.AnnotationConsumerInitializer; -import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; /** * @since 5.0 */ class ParameterizedTestExtension implements TestTemplateInvocationContextProvider { - private static final String METHOD_CONTEXT_KEY = "context"; + static final String METHOD_CONTEXT_KEY = "context"; static final String ARGUMENT_MAX_LENGTH_KEY = "junit.jupiter.params.displayname.argument.maxlength"; static final String DEFAULT_DISPLAY_NAME = "{default_display_name}"; static final String DISPLAY_NAME_PATTERN_KEY = "junit.jupiter.params.displayname.default"; @@ -47,19 +46,21 @@ public boolean supportsTestTemplate(ExtensionContext context) { return false; } - Method testMethod = context.getTestMethod().get(); - if (!isAnnotated(testMethod, ParameterizedTest.class)) { + Method templateMethod = context.getTestMethod().get(); + Optional annotation = findAnnotation(templateMethod, ParameterizedTest.class); + if (!annotation.isPresent()) { return false; } - ParameterizedTestMethodContext methodContext = new ParameterizedTestMethodContext(testMethod); + ParameterizedTestMethodContext methodContext = new ParameterizedTestMethodContext(templateMethod, + annotation.get()); Preconditions.condition(methodContext.hasPotentiallyValidSignature(), () -> String.format( "@ParameterizedTest method [%s] declares formal parameters in an invalid order: " + "argument aggregators must be declared after any indexed arguments " + "and before any arguments resolved by another ParameterResolver.", - testMethod.toGenericString())); + templateMethod.toGenericString())); getStore(context).put(METHOD_CONTEXT_KEY, methodContext); @@ -70,48 +71,41 @@ public boolean supportsTestTemplate(ExtensionContext context) { public Stream provideTestTemplateInvocationContexts( ExtensionContext extensionContext) { - Method templateMethod = extensionContext.getRequiredTestMethod(); - String displayName = extensionContext.getDisplayName(); - ParameterizedTestMethodContext methodContext = getStore(extensionContext)// - .get(METHOD_CONTEXT_KEY, ParameterizedTestMethodContext.class); - int argumentMaxLength = extensionContext.getConfigurationParameter(ARGUMENT_MAX_LENGTH_KEY, - Integer::parseInt).orElse(512); - ParameterizedTestNameFormatter formatter = createNameFormatter(extensionContext, templateMethod, methodContext, - displayName, argumentMaxLength); + ParameterizedTestMethodContext methodContext = getMethodContext(extensionContext); + ParameterizedTestNameFormatter formatter = createNameFormatter(extensionContext, methodContext); AtomicLong invocationCount = new AtomicLong(0); + List argumentsSources = findRepeatableAnnotations(methodContext.method, ArgumentsSource.class); + + Preconditions.notEmpty(argumentsSources, + "Configuration error: You must configure at least one arguments source for this @ParameterizedTest"); + // @formatter:off - return findRepeatableAnnotations(templateMethod, ArgumentsSource.class) + return argumentsSources .stream() .map(ArgumentsSource::value) - .map(this::instantiateArgumentsProvider) - .map(provider -> AnnotationConsumerInitializer.initialize(templateMethod, provider)) + .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentsProvider.class, clazz, extensionContext)) + .map(provider -> AnnotationConsumerInitializer.initialize(methodContext.method, provider)) .flatMap(provider -> arguments(provider, extensionContext)) .map(arguments -> { invocationCount.incrementAndGet(); return createInvocationContext(formatter, methodContext, arguments, invocationCount.intValue()); }) .onClose(() -> - Preconditions.condition(invocationCount.get() > 0, + Preconditions.condition(invocationCount.get() > 0 || methodContext.annotation.allowZeroInvocations(), "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")); // @formatter:on } - @SuppressWarnings("ConstantConditions") - private ArgumentsProvider instantiateArgumentsProvider(Class clazz) { - try { - return ReflectionUtils.newInstance(clazz); - } - catch (Exception ex) { - if (ex instanceof NoSuchMethodException) { - String message = String.format("Failed to find a no-argument constructor for ArgumentsProvider [%s]. " - + "Please ensure that a no-argument constructor exists and " - + "that the class is either a top-level class or a static nested class", - clazz.getName()); - throw new JUnitException(message, ex); - } - throw ex; - } + @Override + public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext extensionContext) { + ParameterizedTestMethodContext methodContext = getMethodContext(extensionContext); + return methodContext.annotation.allowZeroInvocations(); + } + + private ParameterizedTestMethodContext getMethodContext(ExtensionContext extensionContext) { + return getStore(extensionContext)// + .get(METHOD_CONTEXT_KEY, ParameterizedTestMethodContext.class); } private ExtensionContext.Store getStore(ExtensionContext context) { @@ -124,19 +118,24 @@ private TestTemplateInvocationContext createInvocationContext(ParameterizedTestN return new ParameterizedTestInvocationContext(formatter, methodContext, arguments, invocationIndex); } - private ParameterizedTestNameFormatter createNameFormatter(ExtensionContext extensionContext, Method templateMethod, - ParameterizedTestMethodContext methodContext, String displayName, int argumentMaxLength) { + private ParameterizedTestNameFormatter createNameFormatter(ExtensionContext extensionContext, + ParameterizedTestMethodContext methodContext) { - ParameterizedTest parameterizedTest = findAnnotation(templateMethod, ParameterizedTest.class).get(); - String pattern = parameterizedTest.name().equals(DEFAULT_DISPLAY_NAME) - ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY).orElse( - ParameterizedTest.DEFAULT_DISPLAY_NAME) - : parameterizedTest.name(); + String name = methodContext.annotation.name(); + String pattern = name.equals(DEFAULT_DISPLAY_NAME) + ? extensionContext.getConfigurationParameter(DISPLAY_NAME_PATTERN_KEY) // + .orElse(ParameterizedTest.DEFAULT_DISPLAY_NAME) + : name; pattern = Preconditions.notBlank(pattern.trim(), () -> String.format( "Configuration error: @ParameterizedTest on method [%s] must be declared with a non-empty name.", - templateMethod)); - return new ParameterizedTestNameFormatter(pattern, displayName, methodContext, argumentMaxLength); + methodContext.method)); + + int argumentMaxLength = extensionContext.getConfigurationParameter(ARGUMENT_MAX_LENGTH_KEY, Integer::parseInt) // + .orElse(512); + + return new ParameterizedTestNameFormatter(pattern, extensionContext.getDisplayName(), methodContext, + argumentMaxLength); } protected static Stream arguments(ArgumentsProvider provider, ExtensionContext context) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java index c0ac83e78717..a6adfd3e5c5f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestInvocationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,6 @@ package org.junit.jupiter.params; -import static java.util.Collections.singletonList; - import java.util.Arrays; import java.util.List; @@ -47,8 +45,9 @@ public String getDisplayName(int invocationIndex) { @Override public List getAdditionalExtensions() { - return singletonList( - new ParameterizedTestParameterResolver(this.methodContext, this.consumedArguments, this.invocationIndex)); + return Arrays.asList( + new ParameterizedTestParameterResolver(this.methodContext, this.consumedArguments, this.invocationIndex), + new ArgumentCountValidator(this.methodContext, this.arguments)); } private static Object[] consumedArguments(ParameterizedTestMethodContext methodContext, Object[] arguments) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java index 3e20899dddfc..074b32a1b9cb 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestMethodContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,7 +12,7 @@ import static org.junit.jupiter.params.ParameterizedTestMethodContext.ResolverType.AGGREGATOR; import static org.junit.jupiter.params.ParameterizedTestMethodContext.ResolverType.CONVERTER; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -20,6 +20,7 @@ import java.util.List; import java.util.Optional; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.params.aggregator.AggregateWith; @@ -30,9 +31,8 @@ import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.DefaultArgumentConverter; import org.junit.jupiter.params.support.AnnotationConsumerInitializer; -import org.junit.platform.commons.support.ReflectionSupport; -import org.junit.platform.commons.util.AnnotationUtils; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; /** @@ -43,12 +43,17 @@ */ class ParameterizedTestMethodContext { + final Method method; + final ParameterizedTest annotation; + private final Parameter[] parameters; private final Resolver[] resolvers; private final List resolverTypes; - ParameterizedTestMethodContext(Method testMethod) { - this.parameters = testMethod.getParameters(); + ParameterizedTestMethodContext(Method method, ParameterizedTest annotation) { + this.method = Preconditions.notNull(method, "method must not be null"); + this.annotation = Preconditions.notNull(annotation, "annotation must not be null"); + this.parameters = method.getParameters(); this.resolvers = new Resolver[this.parameters.length]; this.resolverTypes = new ArrayList<>(this.parameters.length); for (Parameter parameter : this.parameters) { @@ -161,14 +166,15 @@ int indexOfFirstAggregator() { * Resolve the parameter for the supplied context using the supplied * arguments. */ - Object resolve(ParameterContext parameterContext, Object[] arguments, int invocationIndex) { - return getResolver(parameterContext).resolve(parameterContext, arguments, invocationIndex); + Object resolve(ParameterContext parameterContext, ExtensionContext extensionContext, Object[] arguments, + int invocationIndex) { + return getResolver(parameterContext, extensionContext).resolve(parameterContext, arguments, invocationIndex); } - private Resolver getResolver(ParameterContext parameterContext) { + private Resolver getResolver(ParameterContext parameterContext, ExtensionContext extensionContext) { int index = parameterContext.getIndex(); if (resolvers[index] == null) { - resolvers[index] = resolverTypes.get(index).createResolver(parameterContext); + resolvers[index] = resolverTypes.get(index).createResolver(parameterContext, extensionContext); } return resolvers[index]; } @@ -177,11 +183,11 @@ enum ResolverType { CONVERTER { @Override - Resolver createResolver(ParameterContext parameterContext) { + Resolver createResolver(ParameterContext parameterContext, ExtensionContext extensionContext) { try { // @formatter:off - return AnnotationUtils.findAnnotation(parameterContext.getParameter(), ConvertWith.class) + return AnnotationSupport.findAnnotation(parameterContext.getParameter(), ConvertWith.class) .map(ConvertWith::value) - .map(clazz -> (ArgumentConverter) ReflectionUtils.newInstance(clazz)) + .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentConverter.class, clazz, extensionContext)) .map(converter -> AnnotationConsumerInitializer.initialize(parameterContext.getParameter(), converter)) .map(Converter::new) .orElse(Converter.DEFAULT); @@ -194,11 +200,11 @@ Resolver createResolver(ParameterContext parameterContext) { AGGREGATOR { @Override - Resolver createResolver(ParameterContext parameterContext) { + Resolver createResolver(ParameterContext parameterContext, ExtensionContext extensionContext) { try { // @formatter:off - return AnnotationUtils.findAnnotation(parameterContext.getParameter(), AggregateWith.class) + return AnnotationSupport.findAnnotation(parameterContext.getParameter(), AggregateWith.class) .map(AggregateWith::value) - .map(clazz -> (ArgumentsAggregator) ReflectionSupport.newInstance(clazz)) + .map(clazz -> ParameterizedTestSpiInstantiator.instantiate(ArgumentsAggregator.class, clazz, extensionContext)) .map(Aggregator::new) .orElse(Aggregator.DEFAULT); } // @formatter:on @@ -208,7 +214,7 @@ Resolver createResolver(ParameterContext parameterContext) { } }; - abstract Resolver createResolver(ParameterContext parameterContext); + abstract Resolver createResolver(ParameterContext parameterContext, ExtensionContext extensionContext); } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java index ecbea24fd04c..12cd141ec38c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java index 634405d4a5ce..d49cdc29284e 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,7 +23,7 @@ import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.support.AnnotationSupport; /** * @since 5.0 @@ -44,6 +44,11 @@ class ParameterizedTestParameterResolver implements ParameterResolver, AfterTest this.invocationIndex = invocationIndex; } + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Executable declaringExecutable = parameterContext.getDeclaringExecutable(); @@ -73,7 +78,8 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { - return this.methodContext.resolve(parameterContext, extractPayloads(this.arguments), this.invocationIndex); + return this.methodContext.resolve(parameterContext, extensionContext, extractPayloads(this.arguments), + this.invocationIndex); } /** @@ -81,7 +87,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte */ @Override public void afterTestExecution(ExtensionContext context) { - ParameterizedTest parameterizedTest = AnnotationUtils.findAnnotation(context.getRequiredTestMethod(), + ParameterizedTest parameterizedTest = AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), ParameterizedTest.class).get(); if (!parameterizedTest.autoCloseArguments()) { return; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java new file mode 100644 index 000000000000..1545d292a062 --- /dev/null +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java @@ -0,0 +1,75 @@ +/* + * 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 java.lang.reflect.Constructor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * @since 5.12 + */ +class ParameterizedTestSpiInstantiator { + + static T instantiate(Class spiInterface, Class implementationClass, + ExtensionContext extensionContext) { + + return extensionContext.getExecutableInvoker() // + .invoke(findConstructor(spiInterface, implementationClass)); + } + + @SuppressWarnings("unchecked") + private static Constructor findConstructor(Class spiInterface, + Class implementationClass) { + + return (Constructor) findBestConstructor(spiInterface, implementationClass); + } + + /** + * Find the "best" constructor for the supplied implementation class. + * + *

For backward compatibility, it first checks for a single constructor + * and returns that. If there are multiple constructors, it checks for a + * default constructor which takes precedence over any other constructors. + * Otherwise, this method throws an exception stating that it failed to + * find a suitable constructor. + */ + private static Constructor findBestConstructor(Class spiInterface, + Class implementationClass) { + + Preconditions.condition(!ReflectionUtils.isInnerClass(implementationClass), + () -> String.format("The %s [%s] must be either a top-level class or a static nested class", + spiInterface.getSimpleName(), implementationClass.getName())); + + Constructor[] constructors = implementationClass.getDeclaredConstructors(); + + // Single constructor? + if (constructors.length == 1) { + return constructors[0]; + } + // Find default constructor. + for (Constructor constructor : constructors) { + if (constructor.getParameterCount() == 0) { + return constructor; + } + } + // Otherwise... + String message = String.format( + "Failed to find constructor for %s [%s]. " + + "Please ensure that a no-argument or a single constructor exists.", + spiInterface.getSimpleName(), implementationClass.getName()); + throw new JUnitException(message); + } + +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java index 0510e4df844a..e7fcca21eb50 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/AggregateWith.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java index 07e8b1b8070a..dbdad3cf0d7f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentAccessException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java index 59b1710550f7..5e31a35d288c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java index cb9db4512211..f2c80d0910b7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java index e4ed9803f09f..90ce75ddd048 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/ArgumentsAggregator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; /** * {@code ArgumentsAggregator} is an abstraction for the aggregation of arguments @@ -33,10 +34,11 @@ * in a CSV file into a domain object such as a {@code Person}, {@code Address}, * {@code Order}, etc. * - *

Implementations must provide a no-args constructor and should not make any - * assumptions regarding when they are instantiated or how often they are called. - * Since instances may potentially be cached and called from different threads, - * they should be thread-safe and designed to be used as singletons. + *

Implementations must provide a no-args constructor or a single unambiguous + * constructor to use {@linkplain ParameterResolver parameter resolution}. They + * should not make any assumptions regarding when they are instantiated or how + * often they are called. Since instances may potentially be cached and called + * from different threads, they should be thread-safe. * * @since 5.2 * @see AggregateWith diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java index 39ad3c33ca29..79e1f37999c1 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java index da892c22818d..b100f3ad4854 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/AnnotationBasedArgumentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java index dbcd5b4c1b28..358d6e122c74 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConversionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java index 46f7e9a84b79..78e4cc55e4d6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ArgumentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; /** * {@code ArgumentConverter} is an abstraction that allows an input object to @@ -24,10 +25,11 @@ * method with the help of a * {@link org.junit.jupiter.params.converter.ConvertWith @ConvertWith} annotation. * - *

Implementations must provide a no-args constructor and should not make any - * assumptions regarding when they are instantiated or how often they are called. - * Since instances may potentially be cached and called from different threads, - * they should be thread-safe and designed to be used as singletons. + *

Implementations must provide a no-args constructor or a single unambiguous + * constructor to use {@linkplain ParameterResolver parameter resolution}. They + * should not make any assumptions regarding when they are instantiated or how + * often they are called. Since instances may potentially be cached and called + * from different threads, they should be thread-safe. * *

Extend {@link SimpleArgumentConverter} if your implementation only needs * to know the target type and does not need access to the {@link ParameterContext} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java index b42145b39fa3..d9e7e4fb907d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/ConvertWith.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java index c02fd58dba41..df77d1f759ac 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/DefaultArgumentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java index 26fe98e29df0..6684b472ff18 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -52,7 +52,11 @@ class JavaTimeArgumentConverter extends AnnotationBasedArgumentConverter targetClass, JavaTimeConversionPattern annotation) { if (input == null) { - throw new ArgumentConversionException("Cannot convert null to " + targetClass.getName()); + if (annotation.nullable()) { + return null; + } + throw new ArgumentConversionException( + "Cannot convert null to " + targetClass.getName() + "; consider setting 'nullable = true'"); } TemporalQuery temporalQuery = TEMPORAL_QUERIES.get(targetClass); if (temporalQuery == null) { diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java index d5ec9d509453..d4ab3110e629 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/JavaTimeConversionPattern.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.params.converter; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Documented; @@ -45,4 +46,15 @@ */ String value(); + /** + * Whether {@code null} argument values are allowed. + * + *

Defaults to {@code false}, in which case a {@code null} value will result in + * an exception. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + boolean nullable() default false; + } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java index b24c267e5394..dcf714f5cb84 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/SimpleArgumentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java index bb3c65c009b5..f229572a2a75 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/converter/TypedArgumentConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 f751b35e3c5c..b8ecb2f374dc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java index 94b82972d1b4..bb525eb94648 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/Arguments.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java index 689517d607b8..253e99cbb149 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import org.apiguardian.api.API; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterResolver; /** * An {@code ArgumentsProvider} is responsible for {@linkplain #provideArguments @@ -25,7 +26,8 @@ *

An {@code ArgumentsProvider} can be registered via the * {@link ArgumentsSource @ArgumentsSource} annotation. * - *

Implementations must provide a no-args constructor. + *

Implementations must provide a no-args constructor or a single unambiguous + * constructor to use {@linkplain ParameterResolver parameter resolution}. * * @since 5.0 * @see org.junit.jupiter.params.ParameterizedTest diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java index d3996b704b78..7180cf80ea5a 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java index a5673bae9d87..d40ff40ef6fc 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -31,7 +31,7 @@ * @since 5.0 * @see org.junit.jupiter.params.provider.ArgumentsSource */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.7") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsUtils.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsUtils.java index 7d99a61cc738..105d85354079 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsUtils.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ArgumentsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java index a924cf637229..d248b2dd1cec 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java index 39d286408aa0..acc13160d544 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java index 1798dfc171b3..3c2c8c14a3f3 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -206,7 +206,8 @@ /** * The maximum number of characters allowed per CSV column. * - *

Must be a positive number. + *

Must be a positive number or {@code -1} to allow an unlimited number + * of characters. * *

Defaults to {@code 4096}. * diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java index bc6bf3503fc9..c246d1000020 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ * @see CsvFileSource * @see java.lang.annotation.Repeatable */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java index 0efb81b3252b..ba1b1c0c34ac 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParserFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -77,8 +77,8 @@ private static CsvParserSettings createParserSettings(String delimiter, String l settings.setAutoConfigurationEnabled(false); settings.setIgnoreLeadingWhitespaces(ignoreLeadingAndTrailingWhitespace); settings.setIgnoreTrailingWhitespaces(ignoreLeadingAndTrailingWhitespace); - Preconditions.condition(maxCharsPerColumn > 0, - () -> "maxCharsPerColumn must be a positive number: " + maxCharsPerColumn); + Preconditions.condition(maxCharsPerColumn > 0 || maxCharsPerColumn == -1, + () -> "maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); settings.setMaxCharsPerColumn(maxCharsPerColumn); // Do not use the built-in support for skipping rows/lines since it will // throw an IllegalArgumentException if the file does not contain at least diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java index e0d2275b15c4..b0db31d48b2f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvParsingException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java index ef09eea27ba6..09732e2101f1 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -261,7 +261,8 @@ /** * The maximum number of characters allowed per CSV column. * - *

Must be a positive number. + *

Must be a positive number or {@code -1} to allow an unlimited number + * of characters. * *

Defaults to {@code 4096}. * diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java index 6c6951a75beb..b5e48ab5de00 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ * @see CsvSource * @see java.lang.annotation.Repeatable */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java index 101e5bfc6916..18e9d7d6c7b1 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptyArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java index 780a91dee02a..fef989fc810d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EmptySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java index 0a525289e566..27e2d3a57fc8 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -43,7 +43,19 @@ protected Stream provideArguments(ExtensionContext context, private > Set getEnumConstants(ExtensionContext context, EnumSource enumSource) { Class enumClass = determineEnumClass(context, enumSource); - return EnumSet.allOf(enumClass); + E[] constants = enumClass.getEnumConstants(); + if (constants.length == 0) { + Preconditions.condition(enumSource.from().isEmpty() && enumSource.to().isEmpty(), + "No enum constant in " + enumClass.getSimpleName() + ", but 'from' or 'to' is not empty."); + return EnumSet.noneOf(enumClass); + } + E from = enumSource.from().isEmpty() ? constants[0] : Enum.valueOf(enumClass, enumSource.from()); + E to = enumSource.to().isEmpty() ? constants[constants.length - 1] : Enum.valueOf(enumClass, enumSource.to()); + Preconditions.condition(from.compareTo(to) <= 0, + () -> String.format( + "Invalid enum range: 'from' (%s) must come before 'to' (%s) in the natural order of enum constants.", + from, to)); + return EnumSet.range(from, to); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java index 3bf7e9b88e5e..20eb707d638d 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -40,8 +40,8 @@ * attribute. Otherwise, the declared type of the first parameter of the * {@code @ParameterizedTest} method is used. * - *

The set of enum constants can be restricted via the {@link #names} and - * {@link #mode} attributes. + *

The set of enum constants can be restricted via the {@link #names}, + * {@link #from}, {@link #to} and {@link #mode} attributes. * * @since 5.0 * @see org.junit.jupiter.params.provider.ArgumentsSource @@ -63,6 +63,8 @@ * first parameter of the {@code @ParameterizedTest} method is used. * * @see #names + * @see #from + * @see #to * @see #mode */ Class> value() default NullEnum.class; @@ -71,19 +73,61 @@ * The names of enum constants to provide, or regular expressions to select * the names of enum constants to provide. * - *

If no names or regular expressions are specified, all enum constants - * declared in the specified {@linkplain #value enum type} will be provided. + *

If no names or regular expressions are specified, and neither {@link #from} + * nor {@link #to} are specified, all enum constants declared in the specified + * {@linkplain #value enum type} will be provided. + * + *

If {@link #from} or {@link #to} are specified, the elements in names must + * fall within the range defined by {@link #from} and {@link #to}. * *

The {@link #mode} determines how the names are interpreted. * * @see #value + * @see #from + * @see #to * @see #mode */ String[] names() default {}; + /** + * The starting enum constant of the range to be included. + * + *

Defaults to an empty string, where the range starts from the first enum + * constant of the specified {@linkplain #value enum type}. + * + * @see #value + * @see #names + * @see #to + * @see #mode + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + String from() default ""; + + /** + * The ending enum constant of the range to be included. + * + *

Defaults to an empty string, where the range ends at the last enum + * constant of the specified {@linkplain #value enum type}. + * + * @see #value + * @see #names + * @see #from + * @see #mode + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + String to() default ""; + /** * The enum constant selection mode. * + *

The mode only applies to the {@link #names} attribute and does not change + * the behavior of {@link #from} and {@link #to}, which always define a range + * based on the natural order of the enum constants. + * *

Defaults to {@link Mode#INCLUDE INCLUDE}. * * @see Mode#INCLUDE @@ -92,6 +136,8 @@ * @see Mode#MATCH_ANY * @see Mode#MATCH_NONE * @see #names + * @see #from + * @see #to */ Mode mode() default Mode.INCLUDE; diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java index 22feb5aa46d6..610589378783 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ * @see EnumSource * @see java.lang.annotation.Repeatable */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java index c385d1bb3ba9..8d02863a9e34 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -24,6 +24,8 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassLoaderUtils; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; @@ -87,7 +89,7 @@ static Field findField(Class testClass, String fieldName) { } private static Field validateField(Field field, Object testInstance) { - Preconditions.condition(field.getDeclaringClass().isInstance(testInstance) || ReflectionUtils.isStatic(field), + Preconditions.condition(field.getDeclaringClass().isInstance(testInstance) || ModifierSupport.isStatic(field), () -> format("Field '%s' must be static: local @FieldSource fields must be static " + "unless the PER_CLASS @TestInstance lifecycle mode is used; " + "external @FieldSource fields must always be static.", @@ -96,7 +98,7 @@ private static Field validateField(Field field, Object testInstance) { } private static Object readField(Field field, Object testInstance) { - Object value = ReflectionUtils.tryToReadFieldValue(field, testInstance).getOrThrow( + Object value = ReflectionSupport.tryToReadFieldValue(field, testInstance).getOrThrow( cause -> new JUnitException(format("Could not read field [%s]", field.getName()), cause)); String fieldName = field.getName(); 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 77680a00b7d1..8c2db1a90fb1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java index 0b46746db5e4..f0ca8ad87940 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/FieldSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ * @see FieldSource * @see java.lang.annotation.Repeatable */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = EXPERIMENTAL, since = "5.11") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java index 5915602fba95..3bfced72e817 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,7 @@ import static java.lang.String.format; import static java.util.Arrays.stream; import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream; import java.lang.reflect.Method; 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 977e7555a5d2..2ea6da4da72f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java index 056453f29820..605702827d2f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ * @see MethodSource * @see java.lang.annotation.Repeatable */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java index c5571d3e52f0..d38b2dff4ee4 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullAndEmptySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java index e654ed6b2b47..eef9d19990c6 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java index 1d722287b430..96089872b2db 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullEnum.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java index f842cbde2a6c..3dce2cb097ad 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/NullSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java index e42d448a7415..39bc714671da 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueArgumentsProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java index bc0ed303e935..55d8c50aaa2c 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java index 8db4dcc5b01f..6d52255d9713 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/ValueSources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ * @see ValueSource * @see java.lang.annotation.Repeatable */ -@Target(ElementType.METHOD) +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @API(status = STABLE, since = "5.11") diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java index 6d53313477ec..7549a1c6450f 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java index 9296c70deada..27f6b52853d7 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/support/AnnotationConsumerInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,10 +13,10 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; +import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; diff --git a/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt b/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt index 0c27b52ec08a..7d06dc0aa071 100644 --- a/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt +++ b/junit-jupiter-params/src/main/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java b/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java index 7f3ea0550c58..35569b82dd9d 100644 --- a/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java +++ b/junit-jupiter-params/src/module/org.junit.jupiter.params/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/nativeImage/initialize-at-build-time b/junit-jupiter-params/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..44ca7ffbd8ad --- /dev/null +++ b/junit-jupiter-params/src/nativeImage/initialize-at-build-time @@ -0,0 +1,2 @@ +org.junit.jupiter.params.provider.EnumSource$Mode +org.junit.jupiter.params.provider.EnumSource$Mode$Validator diff --git a/junit-jupiter-params/src/test/README.md b/junit-jupiter-params/src/test/README.md new file mode 100644 index 000000000000..86ed9e49af1d --- /dev/null +++ b/junit-jupiter-params/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `jupiter-tests` project. diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java deleted file mode 100644 index 3bf17b6d5759..000000000000 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2015-2024 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 static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.lang.reflect.Method; -import java.util.Arrays; - -import org.junit.jupiter.api.TestInfo; -import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.CsvToPerson; -import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.Person; -import org.junit.jupiter.params.aggregator.ArgumentsAccessor; -import org.junit.jupiter.params.provider.ValueSource; - -/** - * Unit tests for {@link ParameterizedTestMethodContext}. - * - * @since 5.2 - */ -class ParameterizedTestMethodContextTests { - - @ParameterizedTest - @ValueSource(strings = { "onePrimitive", "twoPrimitives", "twoAggregators", "twoAggregatorsWithTestInfoAtTheEnd", - "mixedMode" }) - void validSignatures(String name) { - assertTrue(new ParameterizedTestMethodContext(method(name)).hasPotentiallyValidSignature()); - } - - @ParameterizedTest - @ValueSource(strings = { "twoAggregatorsWithPrimitiveInTheMiddle", "twoAggregatorsWithTestInfoInTheMiddle" }) - void invalidSignatures(String name) { - assertFalse(new ParameterizedTestMethodContext(method(name)).hasPotentiallyValidSignature()); - } - - private Method method(String name) { - return Arrays.stream(getClass().getDeclaredMethods()) // - .filter(m -> m.getName().equals(name)) // - .findFirst() // - .orElseThrow(); - } - - // --- VALID --------------------------------------------------------------- - - void onePrimitive(int num) { - } - - void twoPrimitives(int num1, int num2) { - } - - void twoAggregators(@CsvToPerson Person person, ArgumentsAccessor arguments) { - } - - void twoAggregatorsWithTestInfoAtTheEnd(@CsvToPerson Person person1, @CsvToPerson Person person2, - TestInfo testInfo) { - } - - void mixedMode(int num1, int num2, ArgumentsAccessor arguments1, ArgumentsAccessor arguments2, - @CsvToPerson Person person1, @CsvToPerson Person person2, TestInfo testInfo1, TestInfo testInfo2) { - } - - // --- INVALID ------------------------------------------------------------- - - void twoAggregatorsWithPrimitiveInTheMiddle(@CsvToPerson Person person1, int num, @CsvToPerson Person person2) { - } - - void twoAggregatorsWithTestInfoInTheMiddle(@CsvToPerson Person person1, TestInfo testInfo, - @CsvToPerson Person person2) { - } - -} diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java deleted file mode 100644 index 6a5312085776..000000000000 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2015-2024 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.provider; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithTwoConstants.BAR; -import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithTwoConstants.FOO; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.util.Arrays; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.provider.EnumSource.Mode; -import org.junit.platform.commons.PreconditionViolationException; - -/** - * @since 5.0 - */ -class EnumArgumentsProviderTests { - - private ExtensionContext extensionContext = mock(); - - @Test - void providesAllEnumConstants() { - var arguments = provideArguments(EnumWithTwoConstants.class); - - assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); - } - - @Test - void provideSingleEnumConstant() { - var arguments = provideArguments(EnumWithTwoConstants.class, "FOO"); - - assertThat(arguments).containsExactly(new Object[] { FOO }); - } - - @Test - void provideAllEnumConstantsWithNamingAll() { - var arguments = provideArguments(EnumWithTwoConstants.class, "FOO", "BAR"); - - assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); - } - - @Test - void duplicateConstantNameIsDetected() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, "FOO", "BAR", "FOO").findAny()); - assertThat(exception).hasMessageContaining("Duplicate enum constant name(s) found"); - } - - @Test - void invalidConstantNameIsDetected() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, "FO0", "B4R").findAny()); - assertThat(exception).hasMessageContaining("Invalid enum constant name(s) in"); - } - - @Test - void invalidPatternIsDetected() { - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(EnumWithTwoConstants.class, Mode.MATCH_ALL, "(", ")").findAny()); - assertThat(exception).hasMessageContaining("Pattern compilation failed"); - } - - @Test - void providesEnumConstantsBasedOnTestMethod() throws Exception { - when(extensionContext.getRequiredTestMethod()).thenReturn( - TestCase.class.getDeclaredMethod("methodWithCorrectParameter", EnumWithTwoConstants.class)); - - var arguments = provideArguments(NullEnum.class); - - assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); - } - - @Test - void incorrectParameterTypeIsDetected() throws Exception { - when(extensionContext.getRequiredTestMethod()).thenReturn( - TestCase.class.getDeclaredMethod("methodWithIncorrectParameter", Object.class)); - - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NullEnum.class).findAny()); - assertThat(exception).hasMessageStartingWith("First parameter must reference an Enum type"); - } - - @Test - void methodsWithoutParametersAreDetected() throws Exception { - when(extensionContext.getRequiredTestMethod()).thenReturn( - TestCase.class.getDeclaredMethod("methodWithoutParameters")); - - Exception exception = assertThrows(PreconditionViolationException.class, - () -> provideArguments(NullEnum.class).findAny()); - assertThat(exception).hasMessageStartingWith("Test method must declare at least one parameter"); - } - - static class TestCase { - void methodWithCorrectParameter(EnumWithTwoConstants parameter) { - } - - void methodWithIncorrectParameter(Object parameter) { - } - - void methodWithoutParameters() { - } - } - - enum EnumWithTwoConstants { - FOO, BAR - } - - private > Stream provideArguments(Class enumClass, String... names) { - return provideArguments(enumClass, Mode.INCLUDE, names); - } - - private > Stream provideArguments(Class enumClass, Mode mode, String... names) { - var annotation = mock(EnumSource.class); - when(annotation.value()).thenAnswer(invocation -> enumClass); - when(annotation.mode()).thenAnswer(invocation -> mode); - when(annotation.names()).thenAnswer(invocation -> names); - when(annotation.toString()).thenReturn(String.format("@EnumSource(value=%s.class, mode=%s, names=%s)", - enumClass.getSimpleName(), mode, Arrays.toString(names))); - - var provider = new EnumArgumentsProvider(); - provider.accept(annotation); - return provider.provideArguments(extensionContext).map(Arguments::get); - } - -} diff --git a/junit-jupiter-params/src/test/resources/log4j2-test.xml b/junit-jupiter-params/src/test/resources/log4j2-test.xml deleted file mode 100644 index 1c1cbb8d6acd..000000000000 --- a/junit-jupiter-params/src/test/resources/log4j2-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/junit-jupiter/src/module/org.junit.jupiter/module-info.java b/junit-jupiter/src/module/org.junit.jupiter/module-info.java index 986039ad9e54..8fd3e579fbaf 100644 --- a/junit-jupiter/src/module/org.junit.jupiter/module-info.java +++ b/junit-jupiter/src/module/org.junit.jupiter/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-commons/junit-platform-commons.gradle.kts b/junit-platform-commons/junit-platform-commons.gradle.kts index c0d1404a6f60..3de45a7edfee 100644 --- a/junit-platform-commons/junit-platform-commons.gradle.kts +++ b/junit-platform-commons/junit-platform-commons.gradle.kts @@ -3,6 +3,7 @@ import junitbuild.java.UpdateJarAction plugins { id("junitbuild.java-library-conventions") id("junitbuild.java-multi-release-sources") + id("junitbuild.native-image-properties") `java-test-fixtures` } @@ -29,6 +30,8 @@ tasks.jar { tasks.codeCoverageClassesJar { exclude("org/junit/platform/commons/util/ModuleUtils.class") + exclude("org/junit/platform/commons/util/PackageNameUtils.class") + exclude("org/junit/platform/commons/util/ServiceLoaderUtils.class") } eclipse { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java index 1b8bf98a7f1e..c09c739bc087 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/JUnitException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java index 1c2f63ac5967..4ae58db3adec 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/PreconditionViolationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java index 0adb7c211964..233f712b6156 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/annotation/Testable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java index 84d5c7a173e9..04403c18a34d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/Try.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java index 5990d37bb869..8f031faf22e0 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/function/package-info.java @@ -1,5 +1,5 @@ /** - * Maintained functional interfaces and support classes. + * Functional interfaces and support classes. */ package org.junit.platform.commons.function; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java index 1368ab3d834e..22a53d59453b 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LogRecordListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java index 016317e7289c..09d4d3c93918 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/Logger.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java index eabab7e93ae6..c6b3ce3b44e4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/logging/LoggerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java index 92ee263134c8..ece10478da6e 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/AnnotationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,14 +10,16 @@ package org.junit.platform.commons.support; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.apiguardian.api.API.Status.STABLE; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; +import java.util.ListIterator; import java.util.Optional; import java.util.function.Predicate; @@ -54,6 +56,10 @@ private AnnotationSupport() { * present or meta-present on the supplied optional * {@code element}. * + *

Note: This method does not find repeatable annotations. + * To check for repeatable annotations, use {@link #findRepeatableAnnotations(Optional, Class)} + * and verify that the returned list is not empty. + * * @param element an {@link Optional} containing the element on which to * search for the annotation; may be {@code null} or empty * @param annotationType the annotation type to search for; never {@code null} @@ -61,6 +67,7 @@ private AnnotationSupport() { * @since 1.3 * @see #isAnnotated(AnnotatedElement, Class) * @see #findAnnotation(Optional, Class) + * @see #findRepeatableAnnotations(Optional, Class) */ @API(status = MAINTAINED, since = "1.3") public static boolean isAnnotated(Optional element, @@ -74,12 +81,17 @@ public static boolean isAnnotated(Optional element, * present or meta-present on the supplied * {@code element}. * + *

Note: This method does not find repeatable annotations. + * To check for repeatable annotations, use {@link #findRepeatableAnnotations(AnnotatedElement, Class)} + * and verify that the returned list is not empty. + * * @param element the element on which to search for the annotation; may be * {@code null} * @param annotationType the annotation type to search for; never {@code null} * @return {@code true} if the annotation is present or meta-present * @see #isAnnotated(Optional, Class) * @see #findAnnotation(AnnotatedElement, Class) + * @see #findRepeatableAnnotations(AnnotatedElement, Class) */ public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { return AnnotationUtils.isAnnotated(element, annotationType); @@ -153,8 +165,14 @@ public static Optional findAnnotation(AnnotatedElement * @since 1.8 * @see SearchOption * @see #findAnnotation(AnnotatedElement, Class) + * @deprecated Use {@link #findAnnotation(Class, Class, List)} + * (for {@code SearchOption.DEFAULT}) or + * {@link #findAnnotation(Class, Class, List)} (for + * {@code SearchOption.INCLUDE_ENCLOSING_CLASSES}) instead */ - @API(status = STABLE, since = "1.10") + @Deprecated + @API(status = DEPRECATED, since = "1.12") + @SuppressWarnings("deprecation") public static Optional findAnnotation(Class clazz, Class annotationType, SearchOption searchOption) { @@ -164,6 +182,56 @@ public static Optional findAnnotation(Class clazz, searchOption == SearchOption.INCLUDE_ENCLOSING_CLASSES); } + /** + * Find the first annotation of the specified type that is either + * directly present, meta-present, or indirectly + * present on the supplied class. + * + *

If the annotation is neither directly present nor meta-present + * on the class, this method will additionally search on interfaces implemented + * by the class before searching for an annotation that is indirectly present + * on the class (i.e., within the class inheritance hierarchy). + * + *

If the annotation still has not been found, this method will optionally + * search recursively through the supplied enclosing instance types, starting + * at the innermost enclosing class (the last one in the supplied list of + * {@code enclosingInstanceTypes}). + * + * @implNote The classes supplied as {@code enclosingInstanceTypes} may + * differ from the classes returned from invocations of + * {@link Class#getEnclosingClass()} — for example, when a nested test + * class is inherited from a superclass. + * + * @param the annotation type + * @param clazz the class on which to search for the annotation; may be {@code null} + * @param annotationType the annotation type to search for; never {@code null} + * @param enclosingInstanceTypes the runtime types of the enclosing + * instances for the class, ordered from outermost to innermost, + * excluding {@code clazz}; never {@code null} + * @return an {@code Optional} containing the annotation; never {@code null} but + * potentially empty if {@code clazz} is not an inner class + * @since 1.12 + * @see #findAnnotation(AnnotatedElement, Class) + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static Optional findAnnotation(Class clazz, Class annotationType, + List> enclosingInstanceTypes) { + + Preconditions.notNull(enclosingInstanceTypes, "enclosingInstanceTypes must not be null"); + + Optional annotation = findAnnotation(clazz, annotationType); + if (!annotation.isPresent()) { + ListIterator> iterator = enclosingInstanceTypes.listIterator(enclosingInstanceTypes.size()); + while (iterator.hasPrevious()) { + annotation = findAnnotation(iterator.previous(), annotationType); + if (annotation.isPresent()) { + break; + } + } + } + return annotation; + } + /** * Find all repeatable {@linkplain Annotation annotations} of the * supplied {@code annotationType} that are either present, diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java index afd5d7583404..f7b7b1ff32f1 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ClassSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathResource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java similarity index 51% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathResource.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java index 720c5166c297..26df8c0f876c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathResource.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/DefaultResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,22 +8,33 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support; + +import static org.apiguardian.api.API.Status.INTERNAL; import java.net.URI; import java.util.Objects; -import org.junit.platform.commons.support.Resource; +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; /** + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * * @since 1.11 */ -class ClasspathResource implements Resource { +@API(status = INTERNAL, since = "1.12") +public class DefaultResource implements Resource { private final String name; private final URI uri; - ClasspathResource(String name, URI uri) { + public DefaultResource(String name, URI uri) { this.name = Preconditions.notNull(name, "name must not be null"); this.uri = Preconditions.notNull(uri, "uri must not be null"); } @@ -44,7 +55,7 @@ public boolean equals(Object o) { return true; if (o == null || getClass() != o.getClass()) return false; - ClasspathResource that = (ClasspathResource) o; + DefaultResource that = (DefaultResource) o; return name.equals(that.name) && uri.equals(that.uri); } @@ -52,4 +63,12 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(name, uri); } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("name", name) // + .append("uri", uri) // + .toString(); + } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java index 7d3aaa26e604..9a424d64e427 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/HierarchyTraversalMode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java index da57dcb22986..21302c9f24b0 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ModifierSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index 6d63e015fccc..5f8d2cbeffc4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ import java.net.URI; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Stream; @@ -67,6 +68,7 @@ private ReflectionSupport() { */ @API(status = DEPRECATED, since = "1.4") @Deprecated + @SuppressWarnings("deprecation") public static Optional> loadClass(String name) { return ReflectionUtils.loadClass(name); } @@ -113,6 +115,53 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) return ReflectionUtils.tryToLoadClass(name, classLoader); } + /** + * Try to get the {@linkplain Resource resources} for the supplied classpath + * resource name. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + * @param classpathResourceName the name of the resource to load; never + * {@code null} or blank + * @return a successful {@code Try} containing the loaded resources or a failed + * {@code Try} containing the exception if no such resources could be loaded; + * never {@code null} + * @since 1.12 + * @see #tryToGetResources(String, ClassLoader) + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static Try> tryToGetResources(String classpathResourceName) { + return ReflectionUtils.tryToGetResources(classpathResourceName); + } + + /** + * Try to load the {@linkplain Resource resources} for the supplied classpath + * resource name, using the supplied {@link ClassLoader}. + * + *

The name of a classpath resource must follow the semantics + * for resource paths as defined in {@link ClassLoader#getResource(String)}. + * + *

If the supplied classpath resource name is prefixed with a slash + * ({@code /}), the slash will be removed. + * + * @param classpathResourceName the name of the resource to load; never + * {@code null} or blank + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @return a successful {@code Try} containing the loaded resources or a failed + * {@code Try} containing the exception if no such resources could be loaded; + * never {@code null} + * @since 1.12 + * @see #tryToGetResources(String) + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { + return ReflectionUtils.tryToGetResources(classpathResourceName, classLoader); + } + /** * Find all {@linkplain Class classes} in the supplied classpath {@code root} * that match the specified {@code classFilter} and {@code classNameFilter} @@ -154,7 +203,6 @@ public static List> findAllClassesInClasspathRoot(URI root, Predicate findAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter); } @@ -201,7 +249,6 @@ public static Stream> streamAllClassesInClasspathRoot(URI root, Predica */ @API(status = EXPERIMENTAL, since = "1.11") public static Stream streamAllResourcesInClasspathRoot(URI root, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter); } @@ -234,7 +281,8 @@ public static List> findAllClassesInPackage(String basePackageName, Pre * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages - * beginning within the supplied base package. + * beginning within the supplied base package. The resulting list may include + * identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java @@ -248,7 +296,6 @@ public static List> findAllClassesInPackage(String basePackageName, Pre */ @API(status = EXPERIMENTAL, since = "1.11") public static List findAllResourcesInPackage(String basePackageName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter); } @@ -258,7 +305,8 @@ public static List findAllResourcesInPackage(String basePackageName, P * predicates. * *

The classpath scanning algorithm searches recursively in subpackages - * beginning within the supplied base package. + * beginning within the supplied base package. The resulting stream may + * include identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java @@ -283,7 +331,8 @@ public static Stream> streamAllClassesInPackage(String basePackageName, * that match the specified {@code resourceFilter} predicate. * *

The classpath scanning algorithm searches recursively in subpackages - * beginning within the supplied base package. + * beginning within the supplied base package. The resulting stream may + * include identically named resources from different classpath roots. * * @param basePackageName the name of the base package in which to start * scanning; must not be {@code null} and must be valid in terms of Java @@ -320,6 +369,7 @@ public static Stream streamAllResourcesInPackage(String basePackageNam * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) * @see #findAllClassesInPackage(String, Predicate, Predicate) */ + @API(status = MAINTAINED, since = "1.1.1") public static List> findAllClassesInModule(String moduleName, Predicate> classFilter, Predicate classNameFilter) { @@ -344,7 +394,6 @@ public static List> findAllClassesInModule(String moduleName, Predicate */ @API(status = EXPERIMENTAL, since = "1.11") public static List findAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter); } @@ -391,7 +440,6 @@ public static Stream> streamAllClassesInModule(String moduleName, Predi */ @API(status = EXPERIMENTAL, since = "1.11") public static Stream streamAllResourcesInModule(String moduleName, Predicate resourceFilter) { - return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter); } @@ -652,4 +700,21 @@ public static Stream> streamNestedClasses(Class clazz, PredicateIf you're looking for similar functionality for constructors or + * methods, consider using {@link #newInstance(Class, Object...)} or + * {@link #invokeMethod(Method, Object, Object...)}. + * + * @param field the field to make accessible; never {@code null} + * @return the supplied field + * @since 1.12 + * @see Field#setAccessible(boolean) + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static Field makeAccessible(Field field) { + return ReflectionUtils.makeAccessible(Preconditions.notNull(field, "field must not be null")); + } + } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java index c9587c3a5ca6..da3d6df59fd1 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/Resource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 8ade7401ed45..4152d14e8220 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package org.junit.platform.commons.support; -import static org.apiguardian.api.API.Status.STABLE; +import static org.apiguardian.api.API.Status.DEPRECATED; import org.apiguardian.api.API; @@ -20,8 +20,10 @@ * @since 1.8 * @see #DEFAULT * @see #INCLUDE_ENCLOSING_CLASSES + * @deprecated because there is only a single non-deprecated search option left */ -@API(status = STABLE, since = "1.10") +@Deprecated +@API(status = DEPRECATED, since = "1.12") public enum SearchOption { /** @@ -37,7 +39,11 @@ public enum SearchOption { * Search the inheritance hierarchy as with the {@link #DEFAULT} search option * but also search the {@linkplain Class#getEnclosingClass() enclosing class} * hierarchy for inner classes (i.e., a non-static member classes). + * + * @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") INCLUDE_ENCLOSING_CLASSES } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java index 54ddc8e11093..1e3474d27c6c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java index 8845d617c7e0..fa95826f5ddd 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/ConversionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java index 680c6aa4712b..aeea2d9e119d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/FallbackStringToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,12 +10,12 @@ package org.junit.platform.commons.support.conversion; -import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP; +import static org.junit.platform.commons.support.ModifierSupport.isNotPrivate; +import static org.junit.platform.commons.support.ModifierSupport.isNotStatic; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; +import static org.junit.platform.commons.support.ReflectionSupport.invokeMethod; import static org.junit.platform.commons.util.ReflectionUtils.findConstructors; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.commons.util.ReflectionUtils.invokeMethod; -import static org.junit.platform.commons.util.ReflectionUtils.isNotPrivate; -import static org.junit.platform.commons.util.ReflectionUtils.isNotStatic; import static org.junit.platform.commons.util.ReflectionUtils.newInstance; import java.lang.reflect.Constructor; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java index fcab61e3a468..4bfefc7b48b1 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToBooleanConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java index 548819a2641d..0f5729a228fc 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCharacterConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java index 36ec2bb211a7..ad16fb18edf3 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToClassConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package org.junit.platform.commons.support.conversion; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; class StringToClassConverter implements StringToObjectConverter { @@ -27,7 +27,7 @@ public Object convert(String source, Class targetType) throws Exception { @Override public Object convert(String className, Class targetType, ClassLoader classLoader) throws Exception { // @formatter:off - return ReflectionUtils.tryToLoadClass(className, classLoader) + return ReflectionSupport.tryToLoadClass(className, classLoader) .getOrThrow(cause -> new ConversionException( "Failed to convert String \"" + className + "\" to type java.lang.Class", cause)); // @formatter:on diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java index f89e67b5fc38..17aa357439ae 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToCommonJavaTypesConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java index 087bb653a974..ee18f8f8b1e3 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToEnumConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java index 753cfba69d89..0e544f39a3a4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToJavaTimeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java index 55c373f7153c..21a7a0fc2e62 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToNumberConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java index b4247f0af832..ceb00e2e95d8 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/StringToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java index e51977179941..18807d6a4e90 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/conversion/package-info.java @@ -1,5 +1,5 @@ /** - * Maintained conversion APIs provided by the JUnit Platform. + * Conversion APIs provided by the JUnit Platform. */ package org.junit.platform.commons.support.conversion; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java index 6e9c460116f4..fae0c2a81547 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/package-info.java @@ -1,5 +1,5 @@ /** - * Maintained common support APIs provided by the JUnit Platform. + * Common support APIs provided by the JUnit Platform. * *

The purpose of this package is to provide {@code TestEngine} and * {@code Extension} authors convenient access to a subset of internal utility diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java similarity index 57% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java index 8b0728213055..c8bc48b6b0a4 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFilter.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClassFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,30 +8,28 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; -import static org.apiguardian.api.API.Status.INTERNAL; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; /** * Class-related predicate used by reflection utilities. * - *

DISCLAIMER

- * - *

These utilities are intended solely for usage within the JUnit framework - * itself. Any usage by external parties is not supported. - * Use at your own risk! - * * @since 1.1 */ -@API(status = INTERNAL, since = "1.1") -public class ClassFilter implements Predicate> { +@API(status = EXPERIMENTAL, since = "1.12") +public class ClassFilter { /** * Create a {@link ClassFilter} instance that accepts all names but filters classes. + * + * @param classPredicate the class type predicate; never {@code null} + * @return an instance of {@code ClassFilter}; never {@code null} */ public static ClassFilter of(Predicate> classPredicate) { return of(name -> true, classPredicate); @@ -39,6 +37,10 @@ public static ClassFilter of(Predicate> classPredicate) { /** * Create a {@link ClassFilter} instance that filters by names and classes. + * + * @param namePredicate the class name predicate; never {@code null} + * @param classPredicate the class type predicate; never {@code null} + * @return an instance of {@code ClassFilter}; never {@code null} */ public static ClassFilter of(Predicate namePredicate, Predicate> classPredicate) { return new ClassFilter(namePredicate, classPredicate); @@ -53,26 +55,25 @@ private ClassFilter(Predicate namePredicate, Predicate> classPr } /** - * Test name using the stored name predicate. + * Test the given name using the stored name predicate. + * + * @param name the name to test; never {@code null} + * @return {@code true} if the input name matches the predicate, otherwise + * {@code false} */ public boolean match(String name) { return namePredicate.test(name); } /** - * Test class using the stored class predicate. + * Test the given class using the stored class predicate. + * + * @param type the type to test; never {@code null} + * @return {@code true} if the input type matches the predicate, otherwise + * {@code false} */ public boolean match(Class type) { return classPredicate.test(type); } - /** - * @implNote This implementation combines all tests stored in the predicates - * of this instance. Any new predicate must be added to this test method as - * well. - */ - @Override - public boolean test(Class type) { - return match(type.getName()) && match(type); - } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java similarity index 94% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java index 7f2ad8195085..2bc01963da99 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFileVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.nio.file.FileVisitResult.CONTINUE; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFilters.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java similarity index 91% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFilters.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java index 7ad6cd3b0682..0ce211f5f924 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFilters.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathFilters.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import java.nio.file.Path; import java.util.function.Predicate; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java new file mode 100644 index 000000000000..344863ad6dac --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/ClasspathScanner.java @@ -0,0 +1,96 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.support.scanning; + +import static org.apiguardian.api.API.Status; + +import java.net.URI; +import java.util.List; +import java.util.function.Predicate; + +import org.apiguardian.api.API; +import org.junit.platform.commons.support.Resource; + +/** + * {@code ClasspathScanner} allows to scan the classpath for classes and + * resources. + * + *

An implementation of this interface can be registered via the + * {@link java.util.ServiceLoader ServiceLoader} mechanism. + * + * @since 1.12 + */ +@API(status = Status.EXPERIMENTAL, since = "1.12") +public interface ClasspathScanner { + + /** + * Find all {@linkplain Class classes} in the supplied classpath {@code root} + * that match the specified {@code classFilter} filter. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param basePackageName the name of the base package in which to start + * scanning; must not be {@code null} and must be valid in terms of Java + * syntax + * @param classFilter the class type filter; never {@code null} + * @return a list of all such classes found; never {@code null} + * but potentially empty + */ + List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter); + + /** + * Find all {@linkplain Class classes} in the supplied classpath {@code root} + * that match the specified {@code classFilter} filter. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param root the URI for the classpath root in which to scan; never + * {@code null} + * @param classFilter the class type filter; never {@code null} + * @return a list of all such classes found; never {@code null} + * but potentially empty + */ + List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter); + + /** + * Find all {@linkplain Resource resources} in the supplied classpath {@code root} + * that match the specified {@code resourceFilter} predicate. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param basePackageName the name of the base package in which to start + * scanning; must not be {@code null} and must be valid in terms of Java + * syntax + * @param resourceFilter the resource type filter; never {@code null} + * @return a list of all such resources found; never {@code null} + * but potentially empty + */ + List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter); + + /** + * Find all {@linkplain Resource resources} in the supplied classpath {@code root} + * that match the specified {@code resourceFilter} predicate. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param root the URI for the classpath root in which to scan; never + * {@code null} + * @param resourceFilter the resource type filter; never {@code null} + * @return a list of all such resources found; never {@code null} + * but potentially empty + */ + List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter); + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java similarity index 97% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java index c1da5bacd819..0cb4977d9477 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/CloseablePath.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/CloseablePath.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.util.Collections.emptyMap; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java similarity index 89% rename from junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java index 19bec125b93c..de9ec7de7b66 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScanner.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/DefaultClasspathScanner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,13 +8,14 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.ClasspathFilters.CLASS_FILE_SUFFIX; +import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.support.scanning.ClasspathFilters.CLASS_FILE_SUFFIX; import static org.junit.platform.commons.util.StringUtils.isNotBlank; import java.io.IOException; @@ -35,11 +36,16 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.DefaultResource; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.PackageUtils; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.UnrecoverableExceptions; /** *

DISCLAIMER

@@ -50,9 +56,10 @@ * * @since 1.0 */ -class ClasspathScanner { +@API(status = INTERNAL, since = "1.12") +public class DefaultClasspathScanner implements ClasspathScanner { - private static final Logger logger = LoggerFactory.getLogger(ClasspathScanner.class); + private static final Logger logger = LoggerFactory.getLogger(DefaultClasspathScanner.class); private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; private static final String CLASSPATH_RESOURCE_PATH_SEPARATOR_STRING = String.valueOf( @@ -69,14 +76,15 @@ class ClasspathScanner { private final BiFunction>> loadClass; - ClasspathScanner(Supplier classLoaderSupplier, + public DefaultClasspathScanner(Supplier classLoaderSupplier, BiFunction>> loadClass) { this.classLoaderSupplier = classLoaderSupplier; this.loadClass = loadClass; } - List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { + @Override + public List> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); @@ -87,14 +95,16 @@ List> scanForClassesInPackage(String basePackageName, ClassFilter class return findClassesForUris(roots, basePackageName, classFilter); } - List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { + @Override + public List> scanForClassesInClasspathRoot(URI root, ClassFilter classFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(classFilter, "classFilter must not be null"); return findClassesForUri(root, PackageUtils.DEFAULT_PACKAGE_NAME, classFilter); } - List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter) { + @Override + public List scanForResourcesInPackage(String basePackageName, Predicate resourceFilter) { Preconditions.condition( PackageUtils.DEFAULT_PACKAGE_NAME.equals(basePackageName) || isNotBlank(basePackageName), "basePackageName must not be null or blank"); @@ -105,7 +115,8 @@ List scanForResourcesInPackage(String basePackageName, Predicate scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter) { + @Override + public List scanForResourcesInClasspathRoot(URI root, Predicate resourceFilter) { Preconditions.notNull(root, "root must not be null"); Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); @@ -188,8 +199,7 @@ private void processClassFileSafely(Path baseDir, String basePackageName, ClassF // @formatter:off loadClass.apply(fullyQualifiedClassName, getClassLoader()) .toOptional() - // Always use ".filter(classFilter)" to include future predicates. - .filter(classFilter) + .filter(classFilter::match) .ifPresent(classConsumer); // @formatter:on } @@ -208,7 +218,7 @@ private void processResourceFileSafely(Path baseDir, String basePackageName, Pre try { String fullyQualifiedResourceName = determineFullyQualifiedResourceName(baseDir, basePackageName, resourceFile); - Resource resource = new ClasspathResource(fullyQualifiedResourceName, resourceFile.toUri()); + Resource resource = new DefaultResource(fullyQualifiedResourceName, resourceFile.toUri()); if (resourceFilter.test(resource)) { resourceConsumer.accept(resource); } @@ -309,7 +319,7 @@ private List getRootUrisForPackageNameOnClassPathAndModulePath(String baseP Set uriSet = new LinkedHashSet<>(getRootUrisForPackage(basePackageName)); if (!basePackageName.isEmpty() && !basePackageName.endsWith(PACKAGE_SEPARATOR_STRING)) { getRootUrisForPackage(basePackageName + PACKAGE_SEPARATOR_STRING).stream() // - .map(ClasspathScanner::removeTrailingClasspathResourcePathSeparator) // + .map(DefaultClasspathScanner::removeTrailingClasspathResourcePathSeparator) // .forEach(uriSet::add); } return new ArrayList<>(uriSet); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java new file mode 100644 index 000000000000..769733a65aef --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/scanning/package-info.java @@ -0,0 +1,5 @@ +/** + * Classpath scanning APIs provided by the JUnit Platform. + */ + +package org.junit.platform.commons.support.scanning; diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java index 5e7157b45c9b..15d47bb253ce 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java index e03fa9d54029..f51c6ca8fc75 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/BlacklistedExceptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java index d90942e77c84..0c4f4eba8cca 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java index 6c713d8c6cb4..efca1d2dcdb6 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassNamePatternFilterUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -41,7 +41,9 @@ private ClassNamePatternFilterUtils() { /* no-op */ } - public static final String DEACTIVATE_ALL_PATTERN = "*"; + public static final String ALL_PATTERN = "*"; + + public static final String BLANK = ""; /** * Create a {@link Predicate} that can be used to exclude (i.e., filter out) @@ -51,7 +53,7 @@ private ClassNamePatternFilterUtils() { * @param patterns a comma-separated list of patterns */ public static Predicate excludeMatchingClasses(String patterns) { - return excludeMatchingClasses(patterns, object -> object.getClass().getName()); + return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.EXCLUDE); } /** @@ -61,26 +63,57 @@ public static Predicate excludeMatchingClasses(String patterns) { * @param patterns a comma-separated list of patterns */ public static Predicate excludeMatchingClassNames(String patterns) { - return excludeMatchingClasses(patterns, Function.identity()); + return matchingClasses(patterns, Function.identity(), FilterType.EXCLUDE); + } + + /** + * Create a {@link Predicate} that can be used to include (i.e., filter in) + * objects of type {@code T} whose fully qualified class names match any of + * the supplied patterns. + * + * @param patterns a comma-separated list of patterns + */ + public static Predicate includeMatchingClasses(String patterns) { + return matchingClasses(patterns, object -> object.getClass().getName(), FilterType.INCLUDE); } - private static Predicate excludeMatchingClasses(String patterns, Function classNameGetter) { + /** + * Create a {@link Predicate} that can be used to include (i.e., filter in) + * fully qualified class names matching any of the supplied patterns. + * + * @param patterns a comma-separated list of patterns + */ + public static Predicate includeMatchingClassNames(String patterns) { + return matchingClasses(patterns, Function.identity(), FilterType.INCLUDE); + } + + private enum FilterType { + INCLUDE, EXCLUDE + } + + private static Predicate matchingClasses(String patterns, Function classNameProvider, + FilterType type) { // @formatter:off return Optional.ofNullable(patterns) .filter(StringUtils::isNotBlank) .map(String::trim) - .map(trimmedPatterns -> createPredicateFromPatterns(trimmedPatterns, classNameGetter)) - .orElse(object -> true); + .map(trimmedPatterns -> createPredicateFromPatterns(trimmedPatterns, classNameProvider, type)) + .orElse(type == FilterType.EXCLUDE ? __ -> true : __ -> false); // @formatter:on } - private static Predicate createPredicateFromPatterns(String patterns, - Function classNameProvider) { - if (DEACTIVATE_ALL_PATTERN.equals(patterns)) { - return object -> false; + private static Predicate createPredicateFromPatterns(String patterns, Function classNameProvider, + FilterType type) { + if (ALL_PATTERN.equals(patterns)) { + return type == FilterType.INCLUDE ? __ -> true : __ -> false; } + List patternList = convertToRegularExpressions(patterns); - return object -> patternList.stream().noneMatch(it -> it.matcher(classNameProvider.apply(object)).matches()); + return object -> { + boolean isMatchingAnyPattern = patternList.stream().anyMatch( + pattern -> pattern.matcher(classNameProvider.apply(object)).matches()); + return (type == FilterType.INCLUDE) == isMatchingAnyPattern; + }; } private static List convertToRegularExpressions(String patterns) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java index 720df1994b93..9fc01426c2f6 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java new file mode 100644 index 000000000000..67c919d930b2 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathScannerLoader.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.StreamSupport.stream; + +import java.util.List; +import java.util.ServiceLoader; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.scanning.ClasspathScanner; +import org.junit.platform.commons.support.scanning.DefaultClasspathScanner; + +/** + * @since 1.12 + */ +class ClasspathScannerLoader { + + static ClasspathScanner getInstance() { + ServiceLoader serviceLoader = ServiceLoader.load(ClasspathScanner.class, + ClassLoaderUtils.getDefaultClassLoader()); + + List classpathScanners = stream(serviceLoader.spliterator(), false).collect(toList()); + + if (classpathScanners.size() == 1) { + return classpathScanners.get(0); + } + + if (classpathScanners.size() > 1) { + throw new JUnitException(String.format( + "There should not be more than one ClasspathScanner implementation present on the classpath but there were %d: %s", + classpathScanners.size(), classpathScanners)); + } + + return new DefaultClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); + } + +} 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 20061af9fd2e..e12d0421f286 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -126,8 +126,7 @@ public static Set toSet(T[] values) { *
 	 * public static <T> Collector<T, ?, List<T>> toUnmodifiableList(Supplier<List<T>> listSupplier) {
 	 *     return Collectors.collectingAndThen(Collectors.toCollection(listSupplier), Collections::unmodifiableList);
-	 * }
-	 * 
+ * } * * @param the type of the input elements * @return a {@code Collector} which collects all the input elements into diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java index b4493a76f64f..c2056951ddf8 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ExceptionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java index 4231c77588e0..9546914e2c6a 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/FunctionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java index 1598826739e9..83a56c172608 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/LruCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java index d24b977d71eb..980c06394ea2 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ModuleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,6 +23,7 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.support.scanning.ClassFilter; /** * Collection of utilities for working with {@code java.lang.Module} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageNameUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageNameUtils.java new file mode 100644 index 000000000000..2a0094dda4c4 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageNameUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import static org.junit.platform.commons.util.PackageUtils.DEFAULT_PACKAGE_NAME; + +/** + * Collection of utilities for working with package names. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.11.3 + */ +class PackageNameUtils { + + static String getPackageName(Class clazz) { + Package p = clazz.getPackage(); + if (p != null) { + return p.getName(); + } + String className = clazz.getName(); + int index = className.lastIndexOf('.'); + return index == -1 ? DEFAULT_PACKAGE_NAME : className.substring(0, index); + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java index 6b23a5324bac..884587c2928d 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PackageUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -40,7 +40,7 @@ private PackageUtils() { /* no-op */ } - static final String DEFAULT_PACKAGE_NAME = ""; + public static final String DEFAULT_PACKAGE_NAME = ""; /** * Get the package attribute for the supplied {@code type} using the diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java index 208db0f299c9..e28c0372d53a 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/PreconditionViolationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java index 33ecd1e91c08..99c8ae49e6e6 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/Preconditions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 517ee073f273..01f0e89d0fd2 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; +import static org.junit.platform.commons.util.PackageNameUtils.getPackageName; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; @@ -35,6 +36,8 @@ import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -59,7 +62,10 @@ import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.DefaultResource; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.support.scanning.ClassFilter; +import org.junit.platform.commons.support.scanning.ClasspathScanner; /** * Collection of utilities for working with the Java reflection APIs. @@ -123,21 +129,6 @@ public enum HierarchyTraversalMode { BOTTOM_UP } - // Pattern: "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - private static final Pattern VM_INTERNAL_OBJECT_ARRAY_PATTERN = Pattern.compile("^(\\[+)L(.+);$"); - - /** - * Pattern: "[x", "[[[[x", etc., where x is Z, B, C, D, F, I, J, S, etc. - * - *

The pattern intentionally captures the last bracket with the - * capital letter so that the combination can be looked up via - * {@link #classNameToTypeMap}. For example, the last matched group - * will contain {@code "[I"} instead of {@code "I"}. - * - * @see Class#getName() - */ - private static final Pattern VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN = Pattern.compile("^(\\[+)(\\[[ZBCDFIJS])$"); - // Pattern: "java.lang.String[]", "int[]", "int[][][][]", etc. // ?> => non-capturing atomic group // ++ => possessive quantifier @@ -145,8 +136,7 @@ public enum HierarchyTraversalMode { private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - private static final ClasspathScanner classpathScanner = new ClasspathScanner( - ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass); + private static final ClasspathScanner classpathScanner = ClasspathScannerLoader.getInstance(); /** * Cache for equivalent methods on an interface implemented by the declaring class. @@ -186,6 +176,7 @@ public enum HierarchyTraversalMode { long.class, float.class, double.class, + void.class, boolean[].class, byte[].class, @@ -213,6 +204,7 @@ public enum HierarchyTraversalMode { Long.class, Float.class, Double.class, + Void.class, String.class, Boolean[].class, @@ -246,7 +238,7 @@ public enum HierarchyTraversalMode { classNameToTypeMap = Collections.unmodifiableMap(classNamesToTypes); - Map, Class> primitivesToWrappers = new IdentityHashMap<>(8); + Map, Class> primitivesToWrappers = new IdentityHashMap<>(9); primitivesToWrappers.put(boolean.class, Boolean.class); primitivesToWrappers.put(byte.class, Byte.class); @@ -373,6 +365,25 @@ public static boolean isInnerClass(Class clazz) { return !isStatic(clazz) && clazz.isMemberClass(); } + /** + * {@return whether the supplied {@code object} is an instance of a record class} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static boolean isRecordObject(Object object) { + return object != null && isRecordClass(object.getClass()); + } + + /** + * {@return whether the supplied {@code clazz} is a record class} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static boolean isRecordClass(Class clazz) { + Class superclass = clazz.getSuperclass(); + return superclass != null && "java.lang.Record".equals(superclass.getName()); + } + /** * Determine if the return type of the supplied method is primitive {@code void}. * @@ -411,8 +422,7 @@ public static boolean isMultidimensionalArray(Object obj) { * *

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

In contrast to {@link Class#isInstance(Object)}, this method returns - * {@code true} if the target type represents a primitive type whose - * wrapper matches the supplied object's type. In addition, this method - * also supports + * {@code true} if the target type represents a primitive type whose wrapper + * matches the supplied object's type. In addition, this method also supports * * widening conversions for primitive types and their corresponding * wrapper types. @@ -850,32 +859,8 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) } return Try.call(() -> { - Matcher matcher; - - // Primitive arrays such as "[I", "[[[[D", etc. - matcher = VM_INTERNAL_PRIMITIVE_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - - // Object arrays such as "[Ljava.lang.String;", "[[[[Ljava.lang.String;", etc. - matcher = VM_INTERNAL_OBJECT_ARRAY_PATTERN.matcher(trimmedName); - if (matcher.matches()) { - String brackets = matcher.group(1); - String componentTypeName = matcher.group(2); - // Calculate dimensions by counting brackets. - int dimensions = brackets.length(); - - return loadArrayType(classLoader, componentTypeName, dimensions); - } - // Arrays such as "java.lang.String[]", "int[]", "int[][][][]", etc. - matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); + Matcher matcher = SOURCE_CODE_SYNTAX_ARRAY_PATTERN.matcher(trimmedName); if (matcher.matches()) { String componentTypeName = matcher.group(1); String bracketPairs = matcher.group(2); @@ -890,6 +875,54 @@ public static Try> tryToLoadClass(String name, ClassLoader classLoader) }); } + /** + * Try to get {@linkplain Resource resources} by their name, using the + * {@link ClassLoaderUtils#getDefaultClassLoader()}. + * + *

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

See {@link org.junit.platform.commons.support.ReflectionSupport#tryToGetResources(String, ClassLoader)} + * for details. + * + * @param classpathResourceName the name of the resources to load; never {@code null} or blank + * @param classLoader the {@code ClassLoader} to use; never {@code null} + * @since 1.12 + */ + @API(status = INTERNAL, since = "1.12") + public static Try> tryToGetResources(String classpathResourceName, ClassLoader classLoader) { + Preconditions.notBlank(classpathResourceName, "Resource name must not be null or blank"); + Preconditions.notNull(classLoader, "Class loader must not be null"); + boolean startsWithSlash = classpathResourceName.startsWith("/"); + String canonicalClasspathResourceName = (startsWithSlash ? classpathResourceName.substring(1) + : classpathResourceName); + + return Try.call(() -> { + List resources = Collections.list(classLoader.getResources(canonicalClasspathResourceName)); + return resources.stream().map(url -> { + try { + return new DefaultResource(canonicalClasspathResourceName, url.toURI()); + } + catch (URISyntaxException e) { + throw ExceptionUtils.throwAsUncheckedException(e); + } + }).collect(toCollection(LinkedHashSet::new)); + }); + } + private static Class loadArrayType(ClassLoader classLoader, String componentTypeName, int dimensions) throws ClassNotFoundException { @@ -933,21 +966,22 @@ public static String getFullyQualifiedMethodName(Class clazz, Method method) */ public static String getFullyQualifiedMethodName(Class clazz, String methodName, Class... parameterTypes) { Preconditions.notNull(clazz, "Class must not be null"); - Preconditions.notBlank(methodName, "Method name must not be null or blank"); return getFullyQualifiedMethodName(clazz.getName(), methodName, ClassUtils.nullSafeToString(parameterTypes)); } /** * Build the fully qualified method name for the method described by the - * supplied class, method name, and parameter types. + * supplied class name, method name, and parameter types. * *

Note that the class is not necessarily the class in which the method is * declared. * - * @param className the name of the class from which the method should be referenced; never {@code null} + * @param className the name of the class from which the method should be referenced; + * never {@code null} * @param methodName the name of the method; never {@code null} or blank - * @param parameterTypeNames the parameter type names of the method; may be empty but not {@code null} + * @param parameterTypeNames the parameter type names of the method; may be + * empty but not {@code null} * @return fully qualified method name; never {@code null} * @since 1.11 */ @@ -1899,7 +1933,7 @@ private static boolean isPackagePrivate(Member member) { } private static boolean declaredInSamePackage(Method m1, Method m2) { - return m1.getDeclaringClass().getPackage().getName().equals(m2.getDeclaringClass().getPackage().getName()); + return getPackageName(m1.getDeclaringClass()).equals(getPackageName(m2.getDeclaringClass())); } /** @@ -1954,7 +1988,7 @@ public static T makeAccessible(T executable) { return executable; } - @API(status = INTERNAL, since = "1.11") + @API(status = INTERNAL, since = "1.12") @SuppressWarnings("deprecation") // "AccessibleObject.isAccessible()" is deprecated in Java 9 public static Field makeAccessible(Field field) { if ((!isPublic(field) || !isPublic(field.getDeclaringClass()) || isFinal(field)) && !field.isAccessible()) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ResourceUtils.java similarity index 70% rename from junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java rename to junit-platform-commons/src/main/java/org/junit/platform/commons/util/ResourceUtils.java index b61598d59b8e..fd7abb29b638 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ResourceUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ResourceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,19 +8,21 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.engine.support.descriptor; +package org.junit.platform.commons.util; + +import static org.apiguardian.api.API.Status.INTERNAL; import java.net.URI; -import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.StringUtils; +import org.apiguardian.api.API; /** * Collection of static utility methods for working with resources. * - * @since 1.3 + * @since 1.3 (originally in org.junit.platform.engine.support.descriptor) */ -final class ResourceUtils { +@API(status = INTERNAL, since = "1.12") +public final class ResourceUtils { private ResourceUtils() { /* no-op */ @@ -33,8 +35,10 @@ private ResourceUtils() { * @param uri the {@code URI} from which to strip the query component * @return a new {@code URI} with the query component removed, or the * original {@code URI} unmodified if it does not have a query component + * + * @since 1.3 */ - static URI stripQueryComponent(URI uri) { + public static URI stripQueryComponent(URI uri) { Preconditions.notNull(uri, "URI must not be null"); if (StringUtils.isBlank(uri.getQuery())) { diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java index 786c5997b2d2..e738aea49d8c 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/RuntimeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java new file mode 100644 index 000000000000..ce006d770099 --- /dev/null +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ServiceLoaderUtils.java @@ -0,0 +1,55 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +import java.util.ServiceLoader; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apiguardian.api.API; + +/** + * Collection of utilities for working with {@link ServiceLoader}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 5.11 + */ +@API(status = API.Status.INTERNAL, since = "5.11") +public class ServiceLoaderUtils { + + private ServiceLoaderUtils() { + /* no-op */ + } + + /** + * Filters the supplied service loader using the supplied predicate. + * + * @param the type of the service + * @param serviceLoader the service loader to be filtered + * @param providerPredicate the predicate to filter the loaded services + * @return a stream of loaded services that match the predicate + */ + public static Stream filter(ServiceLoader serviceLoader, + Predicate> providerPredicate) { + return StreamSupport.stream(serviceLoader.spliterator(), false).filter(it -> { + @SuppressWarnings("unchecked") + Class type = (Class) it.getClass(); + return providerPredicate.test(type); + }); + } + +} diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java index 42dcdeef863b..9989cb33a93b 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/StringUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -306,9 +306,10 @@ private static TwoPartSplitResult splitIntoTwo(String value, int index, int leng public interface TwoPartSplitResult { /** - * Maps the result of splitting a string into two parts or throw an exception. + * Map the result of splitting a string into two parts or throw an exception. * - * @param onePartExceptionCreator the exception creator to use if the string was split into a single part + * @param onePartExceptionCreator the exception creator to use if the string + * was split into a single part * @param twoPartsMapper the mapper to use if the string was split into two parts */ default T mapTwo(Supplier onePartExceptionCreator, @@ -320,7 +321,7 @@ default T mapTwo(Supplier onePartExceptionCreato } /** - * Maps the result of splitting a string into up to two parts. + * Map the result of splitting a string into up to two parts. * * @param onePartMapper the mapper to use if the string was split into a single part * @param twoPartsMapper the mapper to use if the string was split into two parts @@ -340,7 +341,7 @@ private static final class OnePart implements TwoPartSplitResult { @Override public T map(Function onePartMapper, BiFunction twoPartsMapper) { - return onePartMapper.apply(value); + return onePartMapper.apply(this.value); } } @@ -357,7 +358,7 @@ private static final class TwoParts implements TwoPartSplitResult { @Override public T map(Function onePartMapper, BiFunction twoPartsMapper) { - return twoPartsMapper.apply(first, second); + return twoPartsMapper.apply(this.first, this.second); } } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java index f16e23bd4cdf..eabaf3dcc09a 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ToStringBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java index 1235a329450d..23589c2a1672 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/UnrecoverableExceptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java index f3a6bd1a5f33..9f20d964f471 100644 --- a/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java +++ b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ModuleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -37,7 +37,9 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.support.DefaultResource; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.support.scanning.ClassFilter; /** * Collection of utilities for working with {@code java.lang.Module} @@ -225,8 +227,7 @@ List> scan(ModuleReference reference) { .filter(name -> !name.equals("module-info")) .filter(classFilter::match) .map(this::loadClassUnchecked) - // Always use ".filter(classFilter)" to include future predicates. - .filter(classFilter) + .filter(classFilter::match) .collect(Collectors.toList()); // @formatter:on } @@ -298,7 +299,7 @@ List scan(ModuleReference reference) { private Resource loadResourceUnchecked(String binaryName) { try { URI uri = classLoader.getResource(binaryName).toURI(); - return new ClasspathResource(binaryName, uri); + return new DefaultResource(binaryName, uri); } catch (URISyntaxException e) { throw new JUnitException("Failed to load resource with name '" + binaryName + "'.", e); diff --git a/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/PackageNameUtils.java b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/PackageNameUtils.java new file mode 100644 index 000000000000..0cb6aec9e8d8 --- /dev/null +++ b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/PackageNameUtils.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.commons.util; + +/** + * Collection of utilities for working with package names. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 1.11.3 + */ +class PackageNameUtils { + + static String getPackageName(Class clazz) { + return clazz.getPackageName(); + } + +} diff --git a/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ServiceLoaderUtils.java b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ServiceLoaderUtils.java new file mode 100644 index 000000000000..878c62a2c7e7 --- /dev/null +++ b/junit-platform-commons/src/main/java9/org/junit/platform/commons/util/ServiceLoaderUtils.java @@ -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.platform.commons.util; + +import java.util.ServiceLoader; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +/** + * Collection of utilities for working with {@link ServiceLoader}. + * + *

DISCLAIMER

+ * + *

These utilities are intended solely for usage within the JUnit framework + * itself. Any usage by external parties is not supported. + * Use at your own risk! + * + * @since 5.11 + */ +@API(status = Status.INTERNAL, since = "5.11") +public class ServiceLoaderUtils { + + private ServiceLoaderUtils() { + /* no-op */ + } + + /** + * Filters the supplied service loader using the supplied predicate. + * + * @param the type of the service + * @param serviceLoader the service loader to be filtered + * @param providerPredicate the predicate to filter the loaded services + * @return a stream of loaded services that match the predicate + */ + public static Stream filter(ServiceLoader serviceLoader, + Predicate> providerPredicate) { + // @formatter:off + return serviceLoader + .stream() + .filter(provider -> providerPredicate.test(provider.type())) + .map(ServiceLoader.Provider::get); + // @formatter:on + } + +} diff --git a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java index 774684198f9f..176b5fd5c805 100644 --- a/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java +++ b/junit-platform-commons/src/module/org.junit.platform.commons/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 @@ -37,6 +37,7 @@ org.junit.vintage.engine; exports org.junit.platform.commons.support; exports org.junit.platform.commons.support.conversion; + exports org.junit.platform.commons.support.scanning; exports org.junit.platform.commons.util to org.junit.jupiter.api, org.junit.jupiter.engine, @@ -52,4 +53,5 @@ org.junit.platform.suite.engine, org.junit.platform.testkit, org.junit.vintage.engine; + uses org.junit.platform.commons.support.scanning.ClasspathScanner; } diff --git a/junit-platform-commons/src/nativeImage/initialize-at-build-time b/junit-platform-commons/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..a6c384232123 --- /dev/null +++ b/junit-platform-commons/src/nativeImage/initialize-at-build-time @@ -0,0 +1,5 @@ +org.junit.platform.commons.util.StringUtils +org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger +org.junit.platform.commons.logging.LoggerFactory +org.junit.platform.commons.util.ReflectionUtils +org.junit.platform.commons.util.LruCache diff --git a/junit-platform-commons/src/test/README.md b/junit-platform-commons/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-commons/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java index 69f22ef08c40..e0ae6d6c762e 100644 --- a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java +++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/ConcurrencyTestingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java index 62965ffc25df..bdf6aa2521d3 100644 --- a/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java +++ b/junit-platform-commons/src/testFixtures/java/org/junit/platform/commons/test/TestClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 d3d933e10caa..a15f463a4d04 100644 --- a/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts +++ b/junit-platform-console-standalone/junit-platform-console-standalone.gradle.kts @@ -17,6 +17,8 @@ dependencies { shadowed(libs.apiguardian) { because("downstream projects need it to avoid compiler warnings") } + + osgiVerification(libs.openTestReporting.tooling.spi) } val jupiterVersion = rootProject.version @@ -39,11 +41,11 @@ tasks { // https://github.com/junit-team/junit5/issues/761 // prevent duplicates, add 3rd-party licenses explicitly exclude("META-INF/LICENSE*.md") - from(project.projects.junitPlatformConsole.dependencyProject.projectDir) { + from(dependencyProject(project.projects.junitPlatformConsole).projectDir) { include("LICENSE-picocli.md") into("META-INF") } - from(project.projects.junitJupiterParams.dependencyProject.projectDir) { + from(dependencyProject(project.projects.junitJupiterParams).projectDir) { include("LICENSE-univocity-parsers.md") into("META-INF") } diff --git a/junit-platform-console/junit-platform-console.gradle.kts b/junit-platform-console/junit-platform-console.gradle.kts index be24d568d804..cd256959b629 100644 --- a/junit-platform-console/junit-platform-console.gradle.kts +++ b/junit-platform-console/junit-platform-console.gradle.kts @@ -20,13 +20,14 @@ dependencies { osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) + osgiVerification(libs.openTestReporting.tooling.spi) } tasks { compileModule { options.compilerArgs.addAll(listOf( "--add-modules", "org.opentest4j.reporting.events", - "--add-reads", "${project.projects.junitPlatformReporting.dependencyProject.javaModuleName}=org.opentest4j.reporting.events", + "--add-reads", "${project.projects.junitPlatformReporting.javaModuleName}=org.opentest4j.reporting.events", "--add-modules", "info.picocli", "--add-reads", "${javaModuleName}=info.picocli" )) diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java b/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java index 6ffd9ebefa5d..240d6216f61e 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/ConsoleLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -30,36 +30,17 @@ public class ConsoleLauncher { public static void main(String... args) { - PrintWriter out = new PrintWriter(System.out); - PrintWriter err = new PrintWriter(System.err); - CommandResult result = run(out, err, args); + CommandResult result = newCommandFacade().run(args); System.exit(result.getExitCode()); } @API(status = INTERNAL, since = "1.0") public static CommandResult run(PrintWriter out, PrintWriter err, String... args) { - ConsoleLauncher consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, out, err); - return consoleLauncher.run(args); + return newCommandFacade().run(args, out, err); } - private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; - private final PrintWriter out; - private final PrintWriter err; - - ConsoleLauncher(ConsoleTestExecutor.Factory consoleTestExecutorFactory, PrintWriter out, PrintWriter err) { - this.consoleTestExecutorFactory = consoleTestExecutorFactory; - this.out = out; - this.err = err; - } - - CommandResult run(String... args) { - try { - return new CommandFacade(consoleTestExecutorFactory).run(out, err, args); - } - finally { - out.flush(); - err.flush(); - } + private static CommandFacade newCommandFacade() { + return new CommandFacade(ConsoleTestExecutor::new); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/AnsiColorOptionMixin.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/AnsiColorOptionMixin.java index bfd1bdbf1474..dfeb690544fc 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/AnsiColorOptionMixin.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/AnsiColorOptionMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,7 +23,9 @@ class AnsiColorOptionMixin { @Spec(MIXEE) CommandSpec commandSpec; - private boolean disableAnsiColors; + // https://no-color.org + // ANSI is disabled when environment variable NO_COLOR is defined (regardless of its value). + private boolean disableAnsiColors = System.getenv("NO_COLOR") != null; public boolean isDisableAnsiColors() { return disableAnsiColors; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/BannerOptionMixin.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/BannerOptionMixin.java index 0517b760ab48..813bb0937d29 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/BannerOptionMixin.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/BannerOptionMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/BaseCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/BaseCommand.java index d484b182399f..51d29e0c157b 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/BaseCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/BaseCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java index fcc4524df029..123edba1bb7a 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ClasspathEntriesConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java index a591a59abb70..4409fb6ef762 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandFacade.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -33,10 +33,24 @@ public CommandFacade(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { this.consoleTestExecutorFactory = consoleTestExecutorFactory; } - public CommandResult run(PrintWriter out, PrintWriter err, String[] args) { + public CommandResult run(String[] args) { + return run(args, Optional.empty()); + } + + public CommandResult run(String[] args, PrintWriter out, PrintWriter err) { + try { + return run(args, Optional.of(new OutputStreamConfig(out, err))); + } + finally { + out.flush(); + err.flush(); + } + } + + private CommandResult run(String[] args, Optional outputStreamConfig) { Optional version = ManifestVersionProvider.getImplementationVersion(); System.setProperty("junit.docs.version", version.map(it -> it.endsWith("-SNAPSHOT") ? "snapshot" : it).orElse("current")); - return new MainCommand(consoleTestExecutorFactory).run(out, err, args); + return new MainCommand(consoleTestExecutorFactory).run(args, outputStreamConfig); } } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java index 6fe178fc627b..147029e80f1d 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/CommandResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java index 1ff644aabd38..ed82e426dca0 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ConsoleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java index 8371fa1a7644..19008f76425c 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/Details.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/DiscoverTestsCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/DiscoverTestsCommand.java index e1ff06004689..42e476f0a472 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/DiscoverTestsCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/DiscoverTestsCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java index 93e083217de9..caa0340cb76f 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ExecuteTestsCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ListTestEnginesCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ListTestEnginesCommand.java index 875bef485f78..42cfde790699 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ListTestEnginesCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ListTestEnginesCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java index d4a86e807785..cb186813b716 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/MainCommand.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -103,10 +103,11 @@ private Object runCommand(String subcommand, Optional triggeringOption) List args = new ArrayList<>(commandLine.getParseResult().expandedArgs()); triggeringOption.ifPresent(args::remove); - CommandResult result = runCommand(commandLine.getOut(), // - commandLine.getErr(), // + CommandResult result = runCommand( // + new CommandLine(command), // args.toArray(new String[0]), // - command); + Optional.of(new OutputStreamConfig(commandLine)) // + ); this.commandResult = result; printDeprecationWarning(subcommand, triggeringOption, commandLine); @@ -130,24 +131,19 @@ private static void printDeprecationWarning(String subcommand, Optional err.flush(); } - CommandResult run(PrintWriter out, PrintWriter err, String[] args) { + CommandResult run(String[] args, Optional outputStreamConfig) { CommandLine commandLine = new CommandLine(this) // .addSubcommand(new DiscoverTestsCommand(consoleTestExecutorFactory)) // .addSubcommand(new ExecuteTestsCommand(consoleTestExecutorFactory)) // .addSubcommand(new ListTestEnginesCommand()); - return runCommand(out, err, args, commandLine); + return runCommand(commandLine, args, outputStreamConfig); } - private static CommandResult runCommand(PrintWriter out, PrintWriter err, String[] args, Object command) { - return runCommand(out, err, args, new CommandLine(command)); - } - - private static CommandResult runCommand(PrintWriter out, PrintWriter err, String[] args, - CommandLine commandLine) { - int exitCode = BaseCommand.initialize(commandLine) // - .setOut(out) // - .setErr(err) // - .execute(args); + private static CommandResult runCommand(CommandLine commandLine, String[] args, + Optional outputStreamConfig) { + BaseCommand.initialize(commandLine); + outputStreamConfig.ifPresent(it -> it.applyTo(commandLine)); + int exitCode = commandLine.execute(args); return CommandResult.create(exitCode, getLikelyExecutedCommand(commandLine).getExecutionResult()); } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/ManifestVersionProvider.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/ManifestVersionProvider.java index 9b254727fa2f..8f41f9f8c894 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/ManifestVersionProvider.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/ManifestVersionProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/OutputStreamConfig.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/OutputStreamConfig.java new file mode 100644 index 000000000000..8925bddf2f77 --- /dev/null +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/OutputStreamConfig.java @@ -0,0 +1,34 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.console.options; + +import java.io.PrintWriter; + +import picocli.CommandLine; + +class OutputStreamConfig { + + private final PrintWriter out; + private final PrintWriter err; + + OutputStreamConfig(CommandLine commandLine) { + this(commandLine.getOut(), commandLine.getErr()); + } + + OutputStreamConfig(PrintWriter out, PrintWriter err) { + this.out = out; + this.err = err; + } + + void applyTo(CommandLine commandLine) { + commandLine.setOut(out).setErr(err); + } +} diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java index 3bb444602cb8..9f0c139d2c4a 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/SelectorConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,19 +17,25 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; +import java.net.URI; + import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ResourceUtils; import org.junit.platform.engine.DiscoverySelectorIdentifier; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.DirectorySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.FilePosition; import org.junit.platform.engine.discovery.FileSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import picocli.CommandLine.ITypeConverter; @@ -53,8 +59,12 @@ public UriSelector convert(String value) { static class File implements ITypeConverter { @Override public FileSelector convert(String value) { - return selectFile(value); + URI uri = URI.create(value); + String path = ResourceUtils.stripQueryComponent(uri).getPath(); + FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); + return selectFile(path, filePosition); } + } static class Directory implements ITypeConverter { @@ -88,12 +98,14 @@ public MethodSelector convert(String value) { static class ClasspathResource implements ITypeConverter { @Override public ClasspathResourceSelector convert(String value) { - return selectClasspathResource(value); + URI uri = URI.create(value); + String path = ResourceUtils.stripQueryComponent(uri).getPath(); + FilePosition filePosition = FilePosition.fromQuery(uri.getQuery()).orElse(null); + return selectClasspathResource(path, filePosition); } } static class Iteration implements ITypeConverter { - @Override public IterationSelector convert(String value) { DiscoverySelectorIdentifier identifier = DiscoverySelectorIdentifier.create( @@ -101,11 +113,16 @@ public IterationSelector convert(String value) { return (IterationSelector) DiscoverySelectors.parse(identifier) // .orElseThrow(() -> new PreconditionViolationException("Invalid format: Failed to parse selector")); } + } + static class UniqueId implements ITypeConverter { + @Override + public UniqueIdSelector convert(String value) { + return selectUniqueId(value); + } } static class Identifier implements ITypeConverter { - @Override public DiscoverySelectorIdentifier convert(String value) { return DiscoverySelectorIdentifier.parse(value); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java index c0ff2aa5b77d..06067a1022f5 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java index a9f5d00d646e..44ee07588e4a 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestConsoleOutputOptionsMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java index 64dd75973922..06278aace90f 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -35,6 +35,7 @@ import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; /** @@ -58,12 +59,15 @@ public class TestDiscoveryOptions { private List selectedMethods = emptyList(); private List selectedClasspathResources = emptyList(); private List selectedIterations = emptyList(); + private List selectedUniqueIds = emptyList(); private List selectorIdentifiers = emptyList(); private List includedClassNamePatterns = singletonList(STANDARD_INCLUDE_PATTERN); private List excludedClassNamePatterns = emptyList(); private List includedPackages = emptyList(); private List excludedPackages = emptyList(); + private List includedMethodNamePatterns = emptyList(); + private List excludedMethodNamePatterns = emptyList(); private List includedEngines = emptyList(); private List excludedEngines = emptyList(); private List includedTagExpressions = emptyList(); @@ -180,6 +184,14 @@ public void setSelectedIterations(List selectedIterations) { this.selectedIterations = selectedIterations; } + public List getSelectedUniqueIds() { + return selectedUniqueIds; + } + + public void setSelectedUniqueId(List selectedUniqueIds) { + this.selectedUniqueIds = selectedUniqueIds; + } + public List getSelectorIdentifiers() { return selectorIdentifiers; } @@ -235,6 +247,22 @@ public void setExcludedPackages(List excludedPackages) { this.excludedPackages = excludedPackages; } + public List getIncludedMethodNamePatterns() { + return includedMethodNamePatterns; + } + + public void setIncludedMethodNamePatterns(List includedMethodNamePatterns) { + this.includedMethodNamePatterns = includedMethodNamePatterns; + } + + public List getExcludedMethodNamePatterns() { + return excludedMethodNamePatterns; + } + + public void setExcludedMethodNamePatterns(List excludedMethodNamePatterns) { + this.excludedMethodNamePatterns = excludedMethodNamePatterns; + } + public List getIncludedEngines() { return this.includedEngines; } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java index a5fce585dd26..3f91263094b9 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/TestDiscoveryOptionsMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -27,6 +27,7 @@ import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageSelector; +import org.junit.platform.engine.discovery.UniqueIdSelector; import org.junit.platform.engine.discovery.UriSelector; import picocli.CommandLine; @@ -69,79 +70,89 @@ static class SelectorOptions { private boolean scanModulepath2; @Option(names = { "-u", - "--select-uri" }, paramLabel = "URI", arity = "1", converter = SelectorConverter.Uri.class, description = "Select a URI for test discovery. This option can be repeated.") + "--select-uri" }, paramLabel = "URI", arity = "1..*", converter = SelectorConverter.Uri.class, description = "Select a URI for test discovery. This option can be repeated.") private final List selectedUris = new ArrayList<>(); - @Option(names = { "--u", "-select-uri" }, arity = "1", hidden = true, converter = SelectorConverter.Uri.class) + @Option(names = { "--u", + "-select-uri" }, arity = "1..*", hidden = true, converter = SelectorConverter.Uri.class) private final List selectedUris2 = new ArrayList<>(); @Option(names = { "-f", - "--select-file" }, paramLabel = "FILE", arity = "1", converter = SelectorConverter.File.class, description = "Select a file for test discovery. This option can be repeated.") + "--select-file" }, paramLabel = "FILE", arity = "1..*", converter = SelectorConverter.File.class, // + description = "Select a file for test discovery. " + + "The line and column numbers can be provided as URI query parameters (e.g. foo.txt?line=12&column=34). " + + "This option can be repeated.") private final List selectedFiles = new ArrayList<>(); - @Option(names = { "--f", "-select-file" }, arity = "1", hidden = true, converter = SelectorConverter.File.class) + @Option(names = { "--f", + "-select-file" }, arity = "1..*", hidden = true, converter = SelectorConverter.File.class) private final List selectedFiles2 = new ArrayList<>(); @Option(names = { "-d", - "--select-directory" }, paramLabel = "DIR", arity = "1", converter = SelectorConverter.Directory.class, description = "Select a directory for test discovery. This option can be repeated.") + "--select-directory" }, paramLabel = "DIR", arity = "1..*", converter = SelectorConverter.Directory.class, description = "Select a directory for test discovery. This option can be repeated.") private final List selectedDirectories = new ArrayList<>(); @Option(names = { "--d", - "-select-directory" }, arity = "1", hidden = true, converter = SelectorConverter.Directory.class) + "-select-directory" }, arity = "1..*", hidden = true, converter = SelectorConverter.Directory.class) private final List selectedDirectories2 = new ArrayList<>(); @Option(names = { "-o", - "--select-module" }, paramLabel = "NAME", arity = "1", converter = SelectorConverter.Module.class, description = "Select single module for test discovery. This option can be repeated.") + "--select-module" }, paramLabel = "NAME", arity = "1..*", converter = SelectorConverter.Module.class, description = "Select single module for test discovery. This option can be repeated.") private final List selectedModules = new ArrayList<>(); @Option(names = { "--o", - "-select-module" }, arity = "1", converter = SelectorConverter.Module.class, hidden = true) + "-select-module" }, arity = "1..*", converter = SelectorConverter.Module.class, hidden = true) private final List selectedModules2 = new ArrayList<>(); @Option(names = { "-p", - "--select-package" }, paramLabel = "PKG", arity = "1", converter = SelectorConverter.Package.class, description = "Select a package for test discovery. This option can be repeated.") + "--select-package" }, paramLabel = "PKG", arity = "1..*", converter = SelectorConverter.Package.class, description = "Select a package for test discovery. This option can be repeated.") private final List selectedPackages = new ArrayList<>(); @Option(names = { "--p", - "-select-package" }, arity = "1", hidden = true, converter = SelectorConverter.Package.class) + "-select-package" }, arity = "1..*", hidden = true, converter = SelectorConverter.Package.class) private final List selectedPackages2 = new ArrayList<>(); @Option(names = { "-c", - "--select-class" }, paramLabel = "CLASS", arity = "1", converter = SelectorConverter.Class.class, description = "Select a class for test discovery. This option can be repeated.") + "--select-class" }, paramLabel = "CLASS", arity = "1..*", converter = SelectorConverter.Class.class, description = "Select a class for test discovery. This option can be repeated.") private final List selectedClasses = new ArrayList<>(); @Option(names = { "--c", - "-select-class" }, arity = "1", hidden = true, converter = SelectorConverter.Class.class) + "-select-class" }, arity = "1..*", hidden = true, converter = SelectorConverter.Class.class) private final List selectedClasses2 = new ArrayList<>(); @Option(names = { "-m", - "--select-method" }, paramLabel = "NAME", arity = "1", converter = SelectorConverter.Method.class, description = "Select a method for test discovery. This option can be repeated.") + "--select-method" }, paramLabel = "NAME", arity = "1..*", converter = SelectorConverter.Method.class, description = "Select a method for test discovery. This option can be repeated.") private final List selectedMethods = new ArrayList<>(); @Option(names = { "--m", - "-select-method" }, arity = "1", hidden = true, converter = SelectorConverter.Method.class) + "-select-method" }, arity = "1..*", hidden = true, converter = SelectorConverter.Method.class) private final List selectedMethods2 = new ArrayList<>(); @Option(names = { "-r", - "--select-resource" }, paramLabel = "RESOURCE", arity = "1", converter = SelectorConverter.ClasspathResource.class, description = "Select a classpath resource for test discovery. This option can be repeated.") + "--select-resource" }, paramLabel = "RESOURCE", arity = "1..*", converter = SelectorConverter.ClasspathResource.class, description = "Select a classpath resource for test discovery. This option can be repeated.") private final List selectedClasspathResources = new ArrayList<>(); @Option(names = { "--r", - "-select-resource" }, arity = "1", hidden = true, converter = SelectorConverter.ClasspathResource.class) + "-select-resource" }, arity = "1..*", hidden = true, converter = SelectorConverter.ClasspathResource.class) private final List selectedClasspathResources2 = new ArrayList<>(); @Option(names = { "-i", - "--select-iteration" }, paramLabel = "PREFIX:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]", arity = "1", converter = SelectorConverter.Iteration.class, // + "--select-iteration" }, paramLabel = "PREFIX:VALUE[INDEX(..INDEX)?(,INDEX(..INDEX)?)*]", arity = "1..*", converter = SelectorConverter.Iteration.class, // description = "Select iterations for test discovery via a prefixed identifier and a list of indexes or index ranges " + "(e.g. method:com.acme.Foo#m()[1..2] selects the first and second iteration of the m() method in the com.acme.Foo class). " + "This option can be repeated.") private final List selectedIterations = new ArrayList<>(); @Option(names = { "--i", - "-select-iteration" }, arity = "1", hidden = true, converter = SelectorConverter.Iteration.class) + "-select-iteration" }, arity = "1..*", hidden = true, converter = SelectorConverter.Iteration.class) private final List selectedIterations2 = new ArrayList<>(); - @Option(names = "--select", paramLabel = "PREFIX:VALUE", arity = "1", converter = SelectorConverter.Identifier.class, // + @Option(names = { "--select-unique-id", + "--uid" }, paramLabel = "UNIQUE-ID", arity = "1..*", converter = SelectorConverter.UniqueId.class, // + description = "Select a unique id for test discovery. This option can be repeated.") + private final List selectedUniqueIds = new ArrayList<>(); + + @Option(names = "--select", paramLabel = "PREFIX:VALUE", arity = "1..*", converter = SelectorConverter.Identifier.class, // description = "Select via a prefixed identifier (e.g. method:com.acme.Foo#m selects the m() method in the com.acme.Foo class). " + "This option can be repeated.") private final List selectorIdentifiers = new ArrayList<>(); @@ -163,6 +174,7 @@ private void applyTo(TestDiscoveryOptions result) { result.setSelectedClasspathResources( merge(this.selectedClasspathResources, this.selectedClasspathResources2)); result.setSelectedIterations(merge(this.selectedIterations, this.selectedIterations2)); + result.setSelectedUniqueId(this.selectedUniqueIds); result.setSelectorIdentifiers(this.selectorIdentifiers); } } @@ -202,6 +214,16 @@ static class FilterOptions { @Option(names = { "-exclude-package" }, arity = "1", hidden = true) private final List excludePackages2 = new ArrayList<>(); + @Option(names = { + "--include-methodname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to include only methods whose fully qualified names without parameters match. " // + + "When this option is repeated, all patterns will be combined using OR semantics.") + private final List includeMethodNamePatterns = new ArrayList<>(); + + @Option(names = { + "--exclude-methodname" }, paramLabel = "PATTERN", arity = "1", description = "Provide a regular expression to exclude those methods whose fully qualified names without parameters match. " // + + "When this option is repeated, all patterns will be combined using OR semantics.") + private final List excludeMethodNamePatterns = new ArrayList<>(); + @Option(names = { "-t", "--include-tag" }, paramLabel = "TAG", arity = "1", description = "Provide a tag or tag expression to include only tests whose tags match. " + // @@ -239,6 +261,8 @@ private void applyTo(TestDiscoveryOptions result) { result.setExcludedClassNamePatterns(merge(this.excludeClassNamePatterns, this.excludeClassNamePatterns2)); result.setIncludedPackages(merge(this.includePackages, this.includePackages2)); result.setExcludedPackages(merge(this.excludePackages, this.excludePackages2)); + result.setIncludedMethodNamePatterns(new ArrayList<>(this.includeMethodNamePatterns)); + result.setExcludedMethodNamePatterns(new ArrayList<>(this.excludeMethodNamePatterns)); result.setIncludedTagExpressions(merge(this.includedTags, this.includedTags2)); result.setExcludedTagExpressions(merge(this.excludedTags, this.excludedTags2)); result.setIncludedEngines(merge(this.includedEngines, this.includedEngines2)); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java b/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java index b41aaea1f55c..83d75d08fc8f 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/options/Theme.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java index 790d90a61e44..7ff8e3f6809b 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ColorPalette.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java index 1cdddf814e10..ab64005eded8 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/ConsoleTestExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,8 @@ package org.junit.platform.console.tasks; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.console.tasks.DiscoveryRequestCreator.toDiscoveryRequestBuilder; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import java.io.PrintWriter; import java.net.URL; @@ -32,6 +34,7 @@ import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestPlan; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.launcher.core.LauncherFactory; import org.junit.platform.launcher.listeners.SummaryGeneratingListener; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -75,7 +78,7 @@ private void discoverTests(PrintWriter out) { Launcher launcher = launcherSupplier.get(); Optional commandLineTestPrinter = createDetailsPrintingListener(out); - LauncherDiscoveryRequest discoveryRequest = new DiscoveryRequestCreator().toDiscoveryRequest(discoveryOptions); + LauncherDiscoveryRequest discoveryRequest = toDiscoveryRequestBuilder(discoveryOptions).build(); TestPlan testPlan = launcher.discover(discoveryRequest); commandLineTestPrinter.ifPresent(printer -> printer.listTests(testPlan)); @@ -98,8 +101,10 @@ private TestExecutionSummary executeTests(PrintWriter out, Optional report Launcher launcher = launcherSupplier.get(); SummaryGeneratingListener summaryListener = registerListeners(out, reportsDir, launcher); - LauncherDiscoveryRequest discoveryRequest = new DiscoveryRequestCreator().toDiscoveryRequest(discoveryOptions); - launcher.execute(discoveryRequest); + LauncherDiscoveryRequestBuilder discoveryRequestBuilder = toDiscoveryRequestBuilder(discoveryOptions); + reportsDir.ifPresent(dir -> discoveryRequestBuilder.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, + dir.toAbsolutePath().toString())); + launcher.execute(discoveryRequestBuilder.build()); TestExecutionSummary summary = summaryListener.getSummary(); if (summary.getTotalFailureCount() > 0 || outputOptions.getDetails() != Details.NONE) { diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java index 470a983bedf7..0313fe16ca5c 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java index fc2fbd25da5b..b0e895e70dd9 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DetailsPrintingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java index 32a4090bc4e2..75934df963ac 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/DiscoveryRequestCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,8 @@ import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; import static org.junit.platform.launcher.EngineFilter.excludeEngines; import static org.junit.platform.launcher.EngineFilter.includeEngines; +import static org.junit.platform.launcher.MethodFilter.excludeMethodNamePatterns; +import static org.junit.platform.launcher.MethodFilter.includeMethodNamePatterns; import static org.junit.platform.launcher.TagFilter.excludeTags; import static org.junit.platform.launcher.TagFilter.includeTags; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -40,7 +42,6 @@ import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.IterationSelector; import org.junit.platform.engine.discovery.MethodSelector; -import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; /** @@ -48,7 +49,7 @@ */ class DiscoveryRequestCreator { - LauncherDiscoveryRequest toDiscoveryRequest(TestDiscoveryOptions options) { + static LauncherDiscoveryRequestBuilder toDiscoveryRequestBuilder(TestDiscoveryOptions options) { LauncherDiscoveryRequestBuilder requestBuilder = request(); List selectors = createDiscoverySelectors(options); requestBuilder.selectors(selectors); @@ -56,10 +57,10 @@ LauncherDiscoveryRequest toDiscoveryRequest(TestDiscoveryOptions options) { requestBuilder.configurationParameters(options.getConfigurationParameters()); requestBuilder.configurationParametersResources( options.getConfigurationParametersResources().toArray(new String[0])); - return requestBuilder.build(); + return requestBuilder; } - private List createDiscoverySelectors(TestDiscoveryOptions options) { + private static List createDiscoverySelectors(TestDiscoveryOptions options) { List explicitSelectors = options.getExplicitSelectors(); if (options.isScanClasspath()) { Preconditions.condition(explicitSelectors.isEmpty(), @@ -75,12 +76,12 @@ private List createDiscoverySelectors(TestDiscovery "Please specify an explicit selector option or use --scan-class-path or --scan-modules"); } - private List createClasspathRootSelectors(TestDiscoveryOptions options) { + private static List createClasspathRootSelectors(TestDiscoveryOptions options) { Set classpathRoots = determineClasspathRoots(options); return selectClasspathRoots(classpathRoots); } - private Set determineClasspathRoots(TestDiscoveryOptions options) { + private static Set determineClasspathRoots(TestDiscoveryOptions options) { if (options.getSelectedClasspathEntries().isEmpty()) { Set rootDirs = new LinkedHashSet<>(ReflectionUtils.getAllClasspathRootDirectories()); rootDirs.addAll(options.getExistingAdditionalClasspathEntries()); @@ -89,7 +90,7 @@ private Set determineClasspathRoots(TestDiscoveryOptions options) { return new LinkedHashSet<>(options.getSelectedClasspathEntries()); } - private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDiscoveryOptions options, + private static void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDiscoveryOptions options, List selectors) { requestBuilder.filters(includedClassNamePatterns(options, selectors)); @@ -106,6 +107,14 @@ private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDisc requestBuilder.filters(excludePackageNames(options.getExcludedPackages())); } + if (!options.getIncludedMethodNamePatterns().isEmpty()) { + requestBuilder.filters(includeMethodNamePatterns(options.getIncludedMethodNamePatterns())); + } + + if (!options.getExcludedMethodNamePatterns().isEmpty()) { + requestBuilder.filters(excludeMethodNamePatterns(options.getExcludedMethodNamePatterns())); + } + if (!options.getIncludedTagExpressions().isEmpty()) { requestBuilder.filters(includeTags(options.getIncludedTagExpressions())); } @@ -123,7 +132,7 @@ private void addFilters(LauncherDiscoveryRequestBuilder requestBuilder, TestDisc } } - private ClassNameFilter includedClassNamePatterns(TestDiscoveryOptions options, + private static ClassNameFilter includedClassNamePatterns(TestDiscoveryOptions options, List selectors) { Stream patternStreams = Stream.concat( // options.getIncludedClassNamePatterns().stream(), // diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java index 0cbe2abd3352..f1b0e755b2a2 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/FlatPrintingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -73,6 +74,12 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e printlnMessage(Style.REPORTED, "Reported values", entry.toString()); } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + printlnTestDescriptor(Style.REPORTED, "Reported:", testIdentifier); + printlnMessage(Style.REPORTED, "Reported file", file.toString()); + } + private void printlnTestDescriptor(Style style, String message, TestIdentifier testIdentifier) { println(style, "%-10s %s (%s)", message, testIdentifier.getDisplayName(), testIdentifier.getUniqueId()); } diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java index abc1cdd2ce87..72b2dc83ff22 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/Style.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java index f58841d3578c..f0be524edaab 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TestFeedPrintingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java index 8c1217f8cf68..e60a07146fa2 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreeNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; @@ -31,6 +32,7 @@ class TreeNode { private TestIdentifier identifier; private TestExecutionResult result; final Queue reports = new ConcurrentLinkedQueue<>(); + final Queue files = new ConcurrentLinkedQueue<>(); final Queue children = new ConcurrentLinkedQueue<>(); boolean visible; @@ -61,6 +63,11 @@ TreeNode addReportEntry(ReportEntry reportEntry) { return this; } + TreeNode addFileEntry(FileEntry file) { + files.add(file); + return this; + } + TreeNode setResult(TestExecutionResult result) { this.result = result; this.duration = System.currentTimeMillis() - creation; diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java index 8e171d48d3b2..c1997f52d342 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrinter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,6 +21,7 @@ import org.junit.platform.console.options.Theme; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -88,6 +89,7 @@ private void printVisible(TreeNode node, String indent, boolean continuous) { node.reason().ifPresent(reason -> printMessage(Style.SKIPPED, tabbed, reason)); node.reports.forEach(e -> printReportEntry(tabbed, e)); out.println(); + node.files.forEach(e -> printFileEntry(tabbed, e)); } private String tab(TreeNode node, boolean continuous) { @@ -152,6 +154,14 @@ private void printReportEntry(String indent, Map.Entry mapEntry) out.print("`"); } + private void printFileEntry(String indent, FileEntry fileEntry) { + out.print(indent); + out.print(fileEntry.getTimestamp()); + out.print(" "); + out.print(color(Style.SUCCESSFUL, fileEntry.getPath().toUri().toString())); + out.println(); + } + private void printMessage(Style style, String indent, String message) { String[] lines = message.split("\\R"); out.print(" "); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java index 437f7b56733e..49ec53d48ec8 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,6 +17,7 @@ import org.junit.platform.console.options.Theme; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -73,6 +74,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e getNode(testIdentifier).addReportEntry(entry); } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + getNode(testIdentifier).addFileEntry(file); + } + @Override public void listTests(TestPlan testPlan) { root = new TreeNode(testPlan.toString()); diff --git a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java index a8df758dc114..103621853033 100644 --- a/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java +++ b/junit-platform-console/src/main/java/org/junit/platform/console/tasks/VerboseTreePrintingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ import org.junit.platform.console.options.Theme; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -130,6 +131,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e printDetail(Style.REPORTED, "reports", entry.toString()); } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + printDetail(Style.REPORTED, "reports", file.toString()); + } + /** * Print static information about the test identifier. */ diff --git a/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java b/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java index f44f94b41e69..145ebed63fd4 100644 --- a/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java +++ b/junit-platform-console/src/main/java17/org/junit/platform/console/options/ConsoleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java b/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java index 5783bff33454..0291a0bf0fdf 100644 --- a/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java +++ b/junit-platform-console/src/main/java9/org/junit/platform/console/ConsoleLauncherToolProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-console/src/module/org.junit.platform.console/module-info.java b/junit-platform-console/src/module/org.junit.platform.console/module-info.java index 08d28b434f50..166aff5ee795 100644 --- a/junit-platform-console/src/module/org.junit.platform.console/module-info.java +++ b/junit-platform-console/src/module/org.junit.platform.console/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-console/src/test/README.md b/junit-platform-console/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-console/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-engine/junit-platform-engine.gradle.kts b/junit-platform-engine/junit-platform-engine.gradle.kts index 416b227b00c1..ef73763146a5 100644 --- a/junit-platform-engine/junit-platform-engine.gradle.kts +++ b/junit-platform-engine/junit-platform-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.native-image-properties") `java-test-fixtures` } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java index 3ab3e6d2f554..6a29af05e7bf 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/CompositeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java index 498971894992..c28f9229d9ce 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ConfigurationParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java index 7872867244d0..03b5ce65dd7c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoveryFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java index 342f536ca2ce..5284d19ee03e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -33,12 +33,16 @@ public interface DiscoverySelector { /** * Return the {@linkplain DiscoverySelectorIdentifier identifier} of this * selector. - *

- * The returned identifier has to be parsable by a corresponding + * + *

The returned identifier must be parsable by a corresponding * {@link DiscoverySelectorIdentifierParser}. * - * @return the identifier of this selector or empty if it is not supported; - * never {@code null} + *

The default implementation returns {@link Optional#empty()}. Can be + * overridden by concrete implementations. + * + * @return an {@link Optional} containing the identifier of this selector; + * never {@code null} but potentially empty if the selector does not support + * identifiers * @since 1.11 */ @API(status = EXPERIMENTAL, since = "1.11") diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelectorIdentifier.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelectorIdentifier.java index 4fd926e9b27b..0a24baa5f0fe 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelectorIdentifier.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/DiscoverySelectorIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,10 +20,9 @@ import org.junit.platform.commons.util.StringUtils; /** - * Identifier for {@link DiscoverySelector DiscoverySelectors} with a specific - * prefix. - *

- * The {@linkplain #toString() string representation} of an identifier is + * Identifier for a {@link DiscoverySelector} with a specific prefix. + * + *

The {@linkplain #toString() string representation} of an identifier is * intended to be human-readable and is formatted as {@code prefix:value}. * * @since 1.11 @@ -37,7 +36,7 @@ public final class DiscoverySelectorIdentifier { private final String value; /** - * Create a new {@link DiscoverySelectorIdentifier} with the supplied prefix and + * Create a new {@code DiscoverySelectorIdentifier} with the supplied prefix and * value. * * @param prefix the prefix; never {@code null} or blank @@ -51,8 +50,8 @@ public static DiscoverySelectorIdentifier create(String prefix, String value) { * Parse the supplied string representation of a * {@link DiscoverySelectorIdentifier} in the format {@code prefix:value}. * - * @param string the string representation of a {@link DiscoverySelectorIdentifier} - * @return the parsed {@link DiscoverySelectorIdentifier} + * @param string the string representation of a {@code DiscoverySelectorIdentifier} + * @return the parsed {@code DiscoverySelectorIdentifier} * @throws PreconditionViolationException if the supplied string does not * conform to the expected format */ @@ -74,7 +73,7 @@ private DiscoverySelectorIdentifier(String prefix, String value) { * @return the prefix; never {@code null} or blank */ public String getPrefix() { - return prefix; + return this.prefix; } /** @@ -83,7 +82,7 @@ public String getPrefix() { * @return the value; never {@code null} or blank */ public String getValue() { - return value; + return this.value; } @Override @@ -95,12 +94,12 @@ public boolean equals(Object o) { return false; } DiscoverySelectorIdentifier that = (DiscoverySelectorIdentifier) o; - return Objects.equals(prefix, that.prefix) && Objects.equals(value, that.value); + return Objects.equals(this.prefix, that.prefix) && Objects.equals(this.value, that.value); } @Override public int hashCode() { - return Objects.hash(prefix, value); + return Objects.hash(this.prefix, this.value); } /** @@ -109,6 +108,7 @@ public int hashCode() { */ @Override public String toString() { - return String.format("%s:%s", prefix, value); + return String.format("%s:%s", this.prefix, this.value); } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java index f5683f5e67a1..e6af6982e72f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java index 41f6e3b76f18..447790814427 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineDiscoveryRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,11 +10,14 @@ package org.junit.platform.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.List; import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * {@code EngineDiscoveryRequest} provides a {@link TestEngine} access to the @@ -72,10 +75,6 @@ public interface EngineDiscoveryRequest { /** * Get the {@link EngineDiscoveryListener} for this request. * - *

The default implementation returns a no-op listener that ignores all - * calls so that engines that call this methods can be used with an earlier - * version of the JUnit Platform that did not yet include this API. - * * @return the discovery listener; never {@code null} * @since 1.6 */ @@ -84,4 +83,15 @@ default EngineDiscoveryListener getDiscoveryListener() { return EngineDiscoveryListener.NOOP; } + /** + * Get the {@link OutputDirectoryProvider} for this request. + * + * @return the output directory provider; never {@code null} + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + default OutputDirectoryProvider getOutputDirectoryProvider() { + throw new JUnitException( + "OutputDirectoryProvider not available; probably due to unaligned versions of the junit-platform-engine and junit-platform-launcher jars on the classpath/module path."); + } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java index 80727af0e3c2..8affe7aad78c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/EngineExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,12 @@ package org.junit.platform.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -137,4 +139,24 @@ default void executionFinished(TestDescriptor testDescriptor, TestExecutionResul default void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { } + /** + * Can be called for any {@link TestDescriptor} in order to attach a file to + * a test or container — for example: + * + *

    + *
  • Screenshots
  • + *
  • Logs
  • + *
  • Output files written by the code under test
  • + *
+ * + *

The current lifecycle state of the supplied {@code TestDescriptor} is + * not relevant: file events can occur at any time. + * + * @param testDescriptor the descriptor of the test or container to which + * the file entry belongs + * @param file a {@code FileEntry} instance to be attached + */ + @API(status = EXPERIMENTAL, since = "1.12") + default void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java index ce0b9e0c65c7..3d320a0d1c74 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/ExecutionRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,19 +10,25 @@ package org.junit.platform.engine; +import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * Provides a single {@link TestEngine} access to the information necessary to * execute its tests. * *

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

Note: the root descriptor is the * {@code TestDescriptor} returned by @@ -74,19 +113,33 @@ public TestDescriptor getRootTestDescriptor() { } /** - * Get the {@link EngineExecutionListener} to be notified of test execution - * events. + * {@return the {@link EngineExecutionListener} to be notified of test execution + * events} */ public EngineExecutionListener getEngineExecutionListener() { return this.engineExecutionListener; } /** - * Get the {@link ConfigurationParameters} that the engine may use to - * influence test execution. + * {@return the {@link ConfigurationParameters} that the engine may use to + * influence test execution} */ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } + /** + * {@return the {@link OutputDirectoryProvider} for this request for writing + * reports and other output files} + * + * @throws PreconditionViolationException if the output directory provider + * is not available + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public OutputDirectoryProvider getOutputDirectoryProvider() { + return Preconditions.notNull(outputDirectoryProvider, + "No OutputDirectoryProvider was configured for this request"); + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java index a18244ee411b..b500a1320a2b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/Filter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java index 81970ac3daaa..b2bfae49122b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/FilterResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java index 087ed1e93fd7..105bf9d09e7d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/SelectorResolutionResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -115,11 +115,11 @@ public Optional getThrowable() { @Override public String toString() { // @formatter:off - return new ToStringBuilder(this) - .append("status", status) - .append("throwable", throwable) - .toString(); - // @formatter:on + return new ToStringBuilder(this) + .append("status", status) + .append("throwable", throwable) + .toString(); + // @formatter:on } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java index c1f09a6b4c14..0d3df349bffa 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java index 67122ccc9a72..877beaa26411 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java index b3a4b280e36d..4bd69d935d05 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestExecutionResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java index c10479314dfc..136a3bfe929c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java index 956da67c8742..ba0834df4dfa 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/TestTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java index fda16880eeda..acd4fe3d4d2c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueId.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java index cbae6c62a863..2aaebf38eb18 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/UniqueIdFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java index b6de7748547f..ae047beed04c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/AbstractClassNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java index 9d41c36b3f6e..c742daee1410 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java index 2d263b092453..38c412c1e5c1 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClassSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,7 +20,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.DiscoverySelectorIdentifier; @@ -92,8 +92,8 @@ public Class getJavaClass() { if (this.javaClass == null) { // @formatter:off Try> tryToLoadClass = this.classLoader == null - ? ReflectionUtils.tryToLoadClass(this.className) - : ReflectionUtils.tryToLoadClass(this.className, this.classLoader); + ? ReflectionSupport.tryToLoadClass(this.className) + : ReflectionSupport.tryToLoadClass(this.className, this.classLoader); this.javaClass = tryToLoadClass.getOrThrow(cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on @@ -162,5 +162,7 @@ public String getPrefix() { public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectClass(identifier.getValue())); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java index 1a17cda9119b..d703b5da7d2b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathResourceSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,13 +10,21 @@ package org.junit.platform.engine.discovery; +import static java.util.Collections.unmodifiableSet; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; +import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; +import java.util.Set; import org.apiguardian.api.API; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.DiscoverySelector; @@ -34,6 +42,10 @@ * {@linkplain Thread#getContextClassLoader() context class loader} of the * {@linkplain Thread thread} that uses it. * + *

Note: Since Java 9, all resources are on the module path. Either in + * named or unnamed modules. These resources are also considered to be + * classpath resources. + * * @since 1.0 * @see DiscoverySelectors#selectClasspathResource(String) * @see ClasspathRootSelector @@ -44,6 +56,7 @@ public class ClasspathResourceSelector implements DiscoverySelector { private final String classpathResourceName; private final FilePosition position; + private Set classpathResources; ClasspathResourceSelector(String classpathResourceName, FilePosition position) { boolean startsWithSlash = classpathResourceName.startsWith("/"); @@ -51,6 +64,11 @@ public class ClasspathResourceSelector implements DiscoverySelector { this.position = position; } + ClasspathResourceSelector(Set classpathResources) { + this(classpathResources.iterator().next().getName(), null); + this.classpathResources = unmodifiableSet(new LinkedHashSet<>(classpathResources)); + } + /** * Get the name of the selected classpath resource. * @@ -65,6 +83,32 @@ public String getClasspathResourceName() { return this.classpathResourceName; } + /** + * Get the selected {@link Resource resources}. + * + *

If the {@link Resource resources} were not provided, but only their name, + * this method attempts to lazily load the {@link Resource resources} based on + * their name and throws a {@link PreconditionViolationException} if the + * resource cannot be loaded. + * + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public Set getClasspathResources() { + if (this.classpathResources == null) { + Try> tryToGetResource = ReflectionUtils.tryToGetResources(this.classpathResourceName); + Set classpathResources = tryToGetResource.getOrThrow( // + cause -> new PreconditionViolationException( // + "Could not load resource(s) with name: " + this.classpathResourceName, cause)); + if (classpathResources.isEmpty()) { + throw new PreconditionViolationException( + "Could not find any resource(s) with name: " + this.classpathResourceName); + } + this.classpathResources = unmodifiableSet(classpathResources); + } + return this.classpathResources; + } + /** * Get the selected {@code FilePosition} within the classpath resource. */ @@ -100,8 +144,12 @@ public int hashCode() { @Override public String toString() { - return new ToStringBuilder(this).append("classpathResourceName", this.classpathResourceName).append("position", - this.position).toString(); + // @formatter:off + return new ToStringBuilder(this) + .append("classpathResourceName", this.classpathResourceName) + .append("position", this.position) + .toString(); + // @formatter:on } @Override @@ -142,5 +190,7 @@ public Optional parse(DiscoverySelectorIdentifier ide } // )); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java index 815edd8373ae..133572b909ff 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ClasspathRootSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -117,5 +117,7 @@ public Optional parse(DiscoverySelectorIdentifier identif Path path = Paths.get(URI.create(identifier.getValue())); return getFirstElement(DiscoverySelectors.selectClasspathRoots(singleton(path))); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java index 976c20ddb695..fe74b2a1fdc8 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DirectorySelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -136,5 +136,7 @@ public String getPrefix() { public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectDirectory(identifier.getValue())); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParser.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParser.java index 8570c6d5c3cc..a643a21d89fc 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParser.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,10 +19,9 @@ import org.junit.platform.engine.DiscoverySelectorIdentifier; /** - * Parser for {@link DiscoverySelectorIdentifier DiscoverySelectorIdentifiers} - * with a specific prefix. - *

- * Implementations of this interface can be registered using the Java service + * Parser for a {@link DiscoverySelectorIdentifier} with a specific prefix. + * + *

Implementations of this interface can be registered using the Java service * loader mechanism to extend the set of supported prefixes for * {@link DiscoverySelectorIdentifier DiscoverySelectorIdentifiers}. * @@ -33,22 +32,23 @@ public interface DiscoverySelectorIdentifierParser { /** - * Get the prefix that this parser can handle. + * Get the prefix that this parser supports. * - * @return the prefix that this parser can handle; never {@code null} + * @return the prefix that this parser supports; never {@code null} or blank */ String getPrefix(); /** * Parse the supplied {@link DiscoverySelectorIdentifier}. - *

- * The JUnit Platform will only invoke this method if the supplied + * + *

The JUnit Platform will only invoke this method if the supplied * {@link DiscoverySelectorIdentifier} has a prefix that matches the value * returned by {@link #getPrefix()}. * * @param identifier the {@link DiscoverySelectorIdentifier} to parse * @param context the {@link Context} to use for parsing - * @return an {@link Optional} containing the parsed {@link DiscoverySelector}; never {@code null} + * @return an {@link Optional} containing the parsed {@link DiscoverySelector}; + * never {@code null} but potentially empty */ Optional parse(DiscoverySelectorIdentifier identifier, Context context); @@ -59,8 +59,8 @@ interface Context { /** * Parse the supplied selector. - *

- * This method is intended to be used by implementations of + * + *

This method is intended to be used by implementations of * {@link DiscoverySelectorIdentifierParser#parse} for selectors that * contain other selectors. */ diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java index 2f0d79b46735..0f2d195eeb8d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectorIdentifierParsers.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -75,11 +75,13 @@ private enum Singleton { for (DiscoverySelectorIdentifierParser parser : loadedParsers) { DiscoverySelectorIdentifierParser previous = parsersByPrefix.put(parser.getPrefix(), parser); Preconditions.condition(previous == null, - () -> String.format("Duplicate parser for prefix: [%s] candidate a: [%s] candidate b: [%s] ", + () -> String.format("Duplicate parser for prefix: [%s]; candidate a: [%s]; candidate b: [%s]", parser.getPrefix(), requireNonNull(previous).getClass().getName(), parser.getClass().getName())); } this.parsersByPrefix = unmodifiableMap(parsersByPrefix); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java index 5bdbb25fd8d5..299204c35677 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/DiscoverySelectors.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.platform.engine.discovery; +import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; @@ -29,6 +30,8 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoverySelector; @@ -281,6 +284,7 @@ public static List selectClasspathRoots(Set classpa * @param classpathResourceName the name of the classpath resource; never * {@code null} or blank * @see #selectClasspathResource(String, FilePosition) + * @see #selectClasspathResource(Set) * @see ClasspathResourceSelector * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) @@ -310,6 +314,7 @@ public static ClasspathResourceSelector selectClasspathResource(String classpath * {@code null} or blank * @param position the position inside the classpath resource; may be {@code null} * @see #selectClasspathResource(String) + * @see #selectClasspathResource(Set) * @see ClasspathResourceSelector * @see ClassLoader#getResource(String) * @see ClassLoader#getResourceAsStream(String) @@ -317,10 +322,43 @@ public static ClasspathResourceSelector selectClasspathResource(String classpath */ public static ClasspathResourceSelector selectClasspathResource(String classpathResourceName, FilePosition position) { - Preconditions.notBlank(classpathResourceName, "Classpath resource name must not be null or blank"); + Preconditions.notBlank(classpathResourceName, "classpath resource name must not be null or blank"); return new ClasspathResourceSelector(classpathResourceName, position); } + /** + * Create a {@code ClasspathResourceSelector} for the supplied classpath + * resources. + * + *

Since {@linkplain org.junit.platform.engine.TestEngine engines} are not + * expected to modify the classpath, the supplied resource must be on the + * classpath of the + * {@linkplain Thread#getContextClassLoader() context class loader} of the + * {@linkplain Thread thread} that uses the resulting selector. + * + *

Note: Since Java 9, all resources are on the module path. Either in + * named or unnamed modules. These resources are also considered to be + * classpath resources. + * + * @param classpathResources a set of classpath resources; never + * {@code null} or empty. All resources must have the same name, may not + * be {@code null} or blank. + * @since 1.12 + * @see #selectClasspathResource(String, FilePosition) + * @see #selectClasspathResource(String) + * @see ClasspathResourceSelector + * @see ReflectionSupport#tryToGetResources(String) + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static ClasspathResourceSelector selectClasspathResource(Set classpathResources) { + Preconditions.notEmpty(classpathResources, "classpath resources must not be null or empty"); + Preconditions.containsNoNullElements(classpathResources, "individual classpath resources must not be null"); + List resourceNames = classpathResources.stream().map(Resource::getName).distinct().collect(toList()); + Preconditions.condition(resourceNames.size() == 1, "all classpath resources must have the same name"); + Preconditions.notBlank(resourceNames.get(0), "classpath resource names must not be null or blank"); + return new ClasspathResourceSelector(classpathResources); + } + /** * Create a {@code ModuleSelector} for the supplied module name. * @@ -930,8 +968,8 @@ public static UniqueIdSelector selectUniqueId(String uniqueId) { * * @param parentSelector the parent selector to select iterations for; never * {@code null} - * @param iterationIndices the iteration indices to select; never - * {@code null} or empty + * @param iterationIndices the iteration indices to select; never {@code null} + * or empty * @since 1.9 * @see IterationSelector */ @@ -943,11 +981,12 @@ public static IterationSelector selectIteration(DiscoverySelector parentSelector } /** - * Parse the supplied string representation of a - * {@link DiscoverySelectorIdentifier}. + * Parse the supplied string representation of a {@link DiscoverySelectorIdentifier}. * * @param identifier the string representation of a {@code DiscoverySelectorIdentifier}; * never {@code null} or blank + * @return an {@link Optional} containing the corresponding {@link DiscoverySelector}; + * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @@ -961,6 +1000,8 @@ public static Optional parse(String identifier) { * * @param identifier the {@code DiscoverySelectorIdentifier} to parse; * never {@code null} + * @return an {@link Optional} containing the corresponding {@link DiscoverySelector}; + * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @@ -975,6 +1016,8 @@ public static Optional parse(DiscoverySelectorIdent * * @param identifiers the string representations of * {@code DiscoverySelectorIdentifiers} to parse; never {@code null} + * @return a stream of the corresponding {@link DiscoverySelector DiscoverySelectors}; + * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @@ -989,6 +1032,8 @@ public static Stream parseAll(String... identifiers * * @param identifiers the {@code DiscoverySelectorIdentifiers} to parse; * never {@code null} + * @return a stream of the corresponding {@link DiscoverySelector DiscoverySelectors}; + * never {@code null} but potentially empty * @since 1.11 * @see DiscoverySelectorIdentifierParser */ @@ -996,4 +1041,5 @@ public static Stream parseAll(String... identifiers public static Stream parseAll(Collection identifiers) { return DiscoverySelectorIdentifierParsers.parseAll(identifiers); } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java index 9ba34a78d35d..3a1733d00775 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludeClassNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java index 430234ea38da..eb649b634c3f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ExcludePackageNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java index 41a125472cb4..48cb72eb7e73 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FilePosition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java index 6246fa58e4c3..80333c12af9d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/FileSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -112,7 +112,7 @@ public boolean equals(Object o) { @API(status = STABLE, since = "1.3") @Override public int hashCode() { - return Objects.hash(path, position); + return Objects.hash(this.path, this.position); } @Override @@ -158,5 +158,7 @@ public Optional parse(DiscoverySelectorIdentifier identifier, Cont } // )); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java index 4d69e22a9de3..8e233bb002fd 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludeClassNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java index 54e554fb3586..322f840ae94d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IncludePackageNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java index 965a86cdb379..dd603097263b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/IterationSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -65,14 +65,14 @@ private SortedSet toSortedSet(int[] iterationIndices) { * Get the selected parent {@link DiscoverySelector}. */ public DiscoverySelector getParentSelector() { - return parentSelector; + return this.parentSelector; } /** * Get the selected iteration indices. */ public SortedSet getIterationIndices() { - return iterationIndices; + return this.iterationIndices; } @Override @@ -84,12 +84,12 @@ public boolean equals(Object o) { return false; } IterationSelector that = (IterationSelector) o; - return parentSelector.equals(that.parentSelector) && iterationIndices.equals(that.iterationIndices); + return this.parentSelector.equals(that.parentSelector) && this.iterationIndices.equals(that.iterationIndices); } @Override public int hashCode() { - return Objects.hash(parentSelector, iterationIndices); + return Objects.hash(this.parentSelector, this.iterationIndices); } @Override @@ -104,13 +104,14 @@ public String toString() { @Override public Optional toIdentifier() { - return parentSelector.toIdentifier().map(parentSelectorString -> DiscoverySelectorIdentifier.create( // + return this.parentSelector.toIdentifier().map(parentSelectorString -> DiscoverySelectorIdentifier.create( // IdentifierParser.PREFIX, // String.format("%s[%s]", parentSelectorString, formatIterationIndicesAsRanges())) // ); } private String formatIterationIndicesAsRanges() { + class Range { final int start; int end; @@ -120,10 +121,11 @@ class Range { this.end = start; } } + List ranges = new ArrayList<>(); - Range current = new Range(iterationIndices.first()); + Range current = new Range(this.iterationIndices.first()); ranges.add(current); - for (int n : iterationIndices.tailSet(current.start + 1)) { + for (int n : this.iterationIndices.tailSet(current.start + 1)) { if (n == current.end + 1) { current.end = n; } @@ -133,14 +135,14 @@ class Range { } } return ranges.stream() // - .map(r -> { - if (r.start == r.end) { - return String.valueOf(r.start); + .map(range -> { + if (range.start == range.end) { + return String.valueOf(range.start); } - if (r.start == r.end - 1) { - return r.start + "," + r.end; + if (range.start == range.end - 1) { + return range.start + "," + range.end; } - return r.start + ".." + r.end; + return range.start + ".." + range.end; }) // .collect(joining(",")); } @@ -185,5 +187,7 @@ private IntStream parseIndexDefinition(String value) { Integer.parseInt(lastIndex))// ); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java index 4a58eb805a8f..2bbe3ca08411 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/MethodSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,6 +23,7 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ToStringBuilder; @@ -236,8 +237,8 @@ private void lazyLoadJavaClass() { // @formatter:off if (this.javaClass == null) { Try> tryToLoadClass = this.classLoader == null - ? ReflectionUtils.tryToLoadClass(this.className) - : ReflectionUtils.tryToLoadClass(this.className, this.classLoader); + ? ReflectionSupport.tryToLoadClass(this.className) + : ReflectionSupport.tryToLoadClass(this.className, this.classLoader); this.javaClass = tryToLoadClass.getOrThrow(cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); } @@ -249,14 +250,14 @@ private void lazyLoadJavaMethod() { lazyLoadJavaClass(); lazyLoadParameterTypes(); if (this.parameterTypes.length > 0) { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName, + this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName, this.parameterTypes).orElseThrow( () -> new PreconditionViolationException(String.format( "Could not find method with name [%s] and parameter types [%s] in class [%s].", this.methodName, this.parameterTypeNames, this.javaClass.getName()))); } else { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName).orElseThrow( + this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName).orElseThrow( () -> new PreconditionViolationException( String.format("Could not find method with name [%s] in class [%s].", this.methodName, this.javaClass.getName()))); @@ -341,4 +342,5 @@ public Optional parse(DiscoverySelectorIdentifier identifier, Co } } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java index 293654370f8e..0cac7f2e3bfe 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/ModuleSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -102,5 +102,7 @@ public String getPrefix() { public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectModule(identifier.getValue())); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java index cfa1dd6e2500..c61f68d6df18 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedClassSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -180,5 +180,7 @@ public Optional parse(DiscoverySelectorIdentifier identifie return Optional.of( DiscoverySelectors.selectNestedClass(parts.subList(0, parts.size() - 1), parts.get(parts.size() - 1))); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java index ae5d4a69fdc0..25f17d67ca92 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/NestedMethodSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -29,15 +29,14 @@ import org.junit.platform.engine.DiscoverySelectorIdentifier; /** - * A {@link DiscoverySelector} that selects a nested {@link Method} - * or a combination of enclosing classes names, class name, method - * name, and parameter types so that - * {@link org.junit.platform.engine.TestEngine TestEngines} can discover - * tests or containers based on methods. + * A {@link DiscoverySelector} that selects a nested {@link Method} or a + * combination of enclosing class names, class name, method name, and parameter + * types so that {@link org.junit.platform.engine.TestEngine TestEngines} can + * discover tests or containers based on methods. * *

If a Java {@link Method} is provided, the selector will return that * {@linkplain #getMethod() method} and its method name, class name, enclosing - * classes names, and parameter types accordingly. If class names or method names + * class names, and parameter types accordingly. If class names or method names * are provided, this selector will only attempt to lazily load a class or method * if {@link #getEnclosingClasses()}, {@link #getNestedClass()}, * {@link #getMethod()}, or {@link #getParameterTypes()} is invoked. @@ -284,5 +283,7 @@ public Optional parse(DiscoverySelectorIdentifier identifi return Optional.of(DiscoverySelectors.selectNestedMethod(enclosingClassNames, nestedClassName, methodName, parameterTypeNames)); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java index 082cf82a6d11..ecbd3f2d520a 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageNameFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java index fc7da6fb5c6f..8f84886873cf 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/PackageSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -102,5 +102,7 @@ public String getPrefix() { public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectPackage(identifier.getValue())); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java index 08068b1d9ca6..ebf1e35697ba 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UniqueIdSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -103,5 +103,7 @@ public String getPrefix() { public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectUniqueId(identifier.getValue())); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java index 9e6ed0c45c8a..1c7b635a915e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/discovery/UriSelector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -106,5 +106,7 @@ public String getPrefix() { public Optional parse(DiscoverySelectorIdentifier identifier, Context context) { return Optional.of(DiscoverySelectors.selectUri(identifier.getValue())); } + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java new file mode 100644 index 000000000000..13ad5f022b52 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/FileEntry.java @@ -0,0 +1,92 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.reporting; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + +/** + * {@code FileEntry} encapsulates a file or directory to be published to the + * reporting infrastructure. + * + * @since 1.12 + * @see #from(Path, String) + */ +@API(status = EXPERIMENTAL, since = "1.12") +public final class FileEntry { + + /** + * Factory for creating a new {@code FileEntry} from the supplied path and + * media type. + * + * @param path the path to publish; never {@code null} + * @param mediaType the media type of the path to publish; may be + * {@code null} + */ + public static FileEntry from(Path path, String mediaType) { + return new FileEntry(path, mediaType); + } + + private final LocalDateTime timestamp = LocalDateTime.now(); + private final Path path; + private final String mediaType; + + private FileEntry(Path path, String mediaType) { + this.path = Preconditions.notNull(path, "path must not be null"); + this.mediaType = mediaType; + } + + /** + * Get the timestamp for when this {@code FileEntry} was created. + * + * @return when this entry was created; never {@code null} + */ + public LocalDateTime getTimestamp() { + return this.timestamp; + } + + /** + * Get the path to be published. + * + * @return the path to publish; never {@code null} + */ + public Path getPath() { + return path; + } + + /** + * Get the media type of the path to be published. + * + * @return the media type of the path to publish; never {@code null} + */ + public Optional getMediaType() { + return Optional.ofNullable(mediaType); + } + + @Override + public String toString() { + ToStringBuilder builder = new ToStringBuilder(this); + builder.append("timestamp", this.timestamp); + builder.append("path", this.path); + if (this.mediaType != null) { + builder.append("mediaType", this.mediaType); + } + return builder.toString(); + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java new file mode 100644 index 000000000000..60fc831b9822 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/OutputDirectoryProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.reporting; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.io.IOException; +import java.nio.file.Path; + +import org.apiguardian.api.API; +import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.TestDescriptor; + +/** + * Provider of output directories for test engines to write reports and other + * output files to. + * + * @since 1.12 + * @see EngineDiscoveryRequest#getOutputDirectoryProvider() + */ +@API(status = EXPERIMENTAL, since = "1.12") +public interface OutputDirectoryProvider { + + /** + * {@return the root directory for all output files; never {@code null}} + */ + Path getRootDirectory(); + + /** + * Create an output directory for the supplied test descriptor. + * + * @param testDescriptor the test descriptor for which to create an output + * directory; never {@code null} + * @return the output directory + * @throws IOException if the output directory could not be created + */ + Path createOutputDirectory(TestDescriptor testDescriptor) throws IOException; + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java index 740df190eaaa..bdea0b7a9568 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/reporting/ReportEntry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -83,7 +83,7 @@ private void add(String key, String value) { * * @return a copy of the map of key-value pairs; never {@code null} */ - public final Map getKeyValuePairs() { + public Map getKeyValuePairs() { return Collections.unmodifiableMap(this.keyValuePairs); } @@ -94,7 +94,7 @@ public final Map getKeyValuePairs() { * * @return when this entry was created; never {@code null} */ - public final LocalDateTime getTimestamp() { + public LocalDateTime getTimestamp() { return this.timestamp; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java index 53603e8598fe..31454eca91cd 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/config/PrefixedConfigurationParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java index 668456e49f32..e8bc7920792c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java index b47cc78cc2a9..186cf149b07d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClassSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,8 +18,8 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; @@ -175,7 +175,7 @@ public final String getClassName() { public final Class getJavaClass() { if (this.javaClass == null) { // @formatter:off - this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( + this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getOrThrow( cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java index 595dd33cd307..a49f65ed040b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ResourceUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java index adfdb8669730..43a396a221b5 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/CompositeTestSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java index 91a77ee6afa8..54ae69de460a 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DefaultUriSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java index 5c719d17dea0..04cc46369d5f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/DirectorySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java index 7fef1aae35b5..0e8e55a78a2f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/EngineDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java index ddff0d98e898..859006090008 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FilePosition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java index d7b4b74d8bab..0e6e42acba94 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java index 5a338cde508e..2087be665406 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSystemSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java index 79d2574b99f1..3f2f876a49ba 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/MethodSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,8 +18,8 @@ import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestSource; @@ -195,7 +195,7 @@ public final Method getJavaMethod() { private void lazyLoadJavaClass() { if (this.javaClass == null) { // @formatter:off - this.javaClass = ReflectionUtils.tryToLoadClass(this.className).getOrThrow( + this.javaClass = ReflectionSupport.tryToLoadClass(this.className).getOrThrow( cause -> new PreconditionViolationException("Could not load class with name: " + this.className, cause)); // @formatter:on } @@ -206,14 +206,14 @@ private void lazyLoadJavaMethod() { if (this.javaMethod == null) { if (StringUtils.isNotBlank(this.methodParameterTypes)) { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName, + this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName, this.methodParameterTypes).orElseThrow( () -> new PreconditionViolationException(String.format( "Could not find method with name [%s] and parameter types [%s] in class [%s].", this.methodName, this.methodParameterTypes, this.javaClass.getName()))); } else { - this.javaMethod = ReflectionUtils.findMethod(this.javaClass, this.methodName).orElseThrow( + this.javaMethod = ReflectionSupport.findMethod(this.javaClass, this.methodName).orElseThrow( () -> new PreconditionViolationException( String.format("Could not find method with name [%s] in class [%s].", this.methodName, this.javaClass.getName()))); diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java index ab78956614af..c6bca4e9c0ff 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/PackageSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java index bd97bbe609e1..58efdba0a566 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/UriSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import org.apiguardian.api.API; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ResourceUtils; import org.junit.platform.engine.TestSource; /** diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java index c8e9b1843476..6c383e418847 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ClassContainerSelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java index c1a6169293e6..36a939414a7d 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolution.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java index df5611990a78..5ec9b333b206 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/EngineDiscoveryRequestResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,7 @@ package org.junit.platform.engine.support.discovery; import static java.util.stream.Collectors.toCollection; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.util.ArrayList; @@ -19,6 +20,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; +import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.EngineDiscoveryRequest; @@ -26,6 +28,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.discovery.ClassNameFilter; import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; import org.junit.platform.engine.discovery.ClasspathRootSelector; import org.junit.platform.engine.discovery.ModuleSelector; import org.junit.platform.engine.discovery.PackageNameFilter; @@ -160,6 +163,25 @@ public Builder addClassContainerSelectorResolver(Predicate> classFil context -> new ClassContainerSelectorResolver(classFilter, context.getClassNameFilter())); } + /** + * Add a predefined resolver that resolves {@link ClasspathRootSelector + * ClasspathRootSelectors}, {@link ModuleSelector ModuleSelectors}, and + * {@link PackageSelector PackageSelectors} into {@link ClasspathResourceSelector + * ClasspathResourceSelectors} by scanning for resources that satisfy the supplied + * predicate in the respective class containers to this builder. + * + * @param resourceFilter predicate the resolved classes must satisfy; never + * {@code null} + * @return this builder for method chaining + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public Builder addResourceContainerSelectorResolver(Predicate resourceFilter) { + Preconditions.notNull(resourceFilter, "resourceFilter must not be null"); + return addSelectorResolver( + context -> new ResourceContainerSelectorResolver(resourceFilter, context.getPackageFilter())); + } + /** * Add a context insensitive {@link SelectorResolver} to this builder. * @@ -247,6 +269,18 @@ public interface InitializationContext { */ Predicate getClassNameFilter(); + /** + * Get the package name filter built from the {@link PackageNameFilter + * PackageNameFilters} in the {@link EngineDiscoveryRequest} that is + * about to be resolved. + * + * @return the predicate for filtering the resolved resource names; never + * {@code null} + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + Predicate getPackageFilter(); + } private static class DefaultInitializationContext implements InitializationContext { @@ -254,11 +288,13 @@ private static class DefaultInitializationContext impl private final EngineDiscoveryRequest request; private final T engineDescriptor; private final Predicate classNameFilter; + private final Predicate packageFilter; DefaultInitializationContext(EngineDiscoveryRequest request, T engineDescriptor) { this.request = request; this.engineDescriptor = engineDescriptor; this.classNameFilter = buildClassNamePredicate(request); + this.packageFilter = buildPackagePredicate(request); } /** @@ -274,6 +310,12 @@ private Predicate buildClassNamePredicate(EngineDiscoveryRequest request return Filter.composeFilters(filters).toPredicate(); } + private Predicate buildPackagePredicate(EngineDiscoveryRequest request) { + List> filters = new ArrayList<>(); + filters.addAll(request.getFiltersByType(PackageNameFilter.class)); + return Filter.composeFilters(filters).toPredicate(); + } + @Override public EngineDiscoveryRequest getDiscoveryRequest() { return request; @@ -288,6 +330,11 @@ public T getEngineDescriptor() { public Predicate getClassNameFilter() { return classNameFilter; } + + @Override + public Predicate getPackageFilter() { + return packageFilter; + } } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java new file mode 100644 index 000000000000..34beef727045 --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolver.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toSet; +import static org.junit.platform.commons.support.ReflectionSupport.findAllResourcesInClasspathRoot; +import static org.junit.platform.commons.support.ReflectionSupport.findAllResourcesInPackage; +import static org.junit.platform.commons.util.ReflectionUtils.findAllResourcesInModule; +import static org.junit.platform.engine.support.discovery.ResourceUtils.packageName; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.selectors; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.unresolved; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.platform.commons.support.Resource; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.discovery.ClasspathRootSelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.ModuleSelector; +import org.junit.platform.engine.discovery.PackageSelector; + +/** + * @since 1.12 + */ +class ResourceContainerSelectorResolver implements SelectorResolver { + private final Predicate resourceFilter; + + ResourceContainerSelectorResolver(Predicate resourceFilter, Predicate packageFilter) { + this.resourceFilter = packageName(packageFilter).and(resourceFilter); + } + + @Override + public Resolution resolve(ClasspathRootSelector selector, Context context) { + return resourceSelectors(findAllResourcesInClasspathRoot(selector.getClasspathRoot(), resourceFilter)); + } + + @Override + public Resolution resolve(ModuleSelector selector, Context context) { + return resourceSelectors(findAllResourcesInModule(selector.getModuleName(), resourceFilter)); + } + + @Override + public Resolution resolve(PackageSelector selector, Context context) { + return resourceSelectors(findAllResourcesInPackage(selector.getPackageName(), resourceFilter)); + } + + private Resolution resourceSelectors(List resources) { + Set selectors = resources.stream() // + .collect(groupingBy(Resource::getName)) // + .values() // + .stream() // + .map(LinkedHashSet::new) // + .map(DiscoverySelectors::selectClasspathResource) // + .collect(toSet()); + + if (selectors.isEmpty()) { + return unresolved(); + } + return selectors(selectors); + } + +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java new file mode 100644 index 000000000000..127b2503952d --- /dev/null +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/ResourceUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import java.util.function.Predicate; + +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.support.Resource; + +/** + * Resource-related utilities to be used in conjunction with {@link ReflectionSupport}. + * + * @since 1.12 + */ +class ResourceUtils { + public static final String DEFAULT_PACKAGE_NAME = ""; + private static final char CLASSPATH_RESOURCE_PATH_SEPARATOR = '/'; + private static final char PACKAGE_SEPARATOR_CHAR = '.'; + + /** + * Match resources against a package filter. + * + *

The {@code /} separated path of a resource is rewritten to a + * {@code .} separated package names. The package filter is applied to that + * package name. + */ + static Predicate packageName(Predicate packageFilter) { + return resource -> packageFilter.test(packageName(resource.getName())); + } + + private static String packageName(String classpathResourceName) { + int lastIndexOf = classpathResourceName.lastIndexOf(CLASSPATH_RESOURCE_PATH_SEPARATOR); + if (lastIndexOf < 0) { + return DEFAULT_PACKAGE_NAME; + } + // classpath resource names do not start with / + String resourcePackagePath = classpathResourceName.substring(0, lastIndexOf); + return resourcePackagePath.replace(CLASSPATH_RESOURCE_PATH_SEPARATOR, PACKAGE_SEPARATOR_CHAR); + } +} diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java index 4685fb9b48c9..a402a4679152 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/discovery/SelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java index e3d2b0392cb2..1eabfbdd8475 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/filter/ClasspathScanningSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,7 +18,7 @@ import java.util.function.Predicate; import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.discovery.ClassNameFilter; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java index df5ea0a5f3e3..76dbbb6b984c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/CompositeLock.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,25 +10,41 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.Collections.unmodifiableList; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.locks.Lock; +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ToStringBuilder; + /** * @since 1.3 */ class CompositeLock implements ResourceLock { + private final List resources; private final List locks; + private final boolean exclusive; + + CompositeLock(List resources, List locks) { + Preconditions.condition(resources.size() == locks.size(), "Resources and locks must have the same size"); + this.resources = unmodifiableList(resources); + this.locks = Preconditions.notEmpty(locks, "Locks must not be empty"); + this.exclusive = resources.stream().anyMatch( + resource -> resource.getLockMode() == ExclusiveResource.LockMode.READ_WRITE); + } - CompositeLock(List locks) { - this.locks = locks; + @Override + public List getResources() { + return resources; } // for tests only List getLocks() { - return locks; + return this.locks; } @Override @@ -38,9 +54,9 @@ public ResourceLock acquire() throws InterruptedException { } private void acquireAllLocks() throws InterruptedException { - List acquiredLocks = new ArrayList<>(locks.size()); + List acquiredLocks = new ArrayList<>(this.locks.size()); try { - for (Lock lock : locks) { + for (Lock lock : this.locks) { lock.lockInterruptibly(); acquiredLocks.add(lock); } @@ -53,7 +69,7 @@ private void acquireAllLocks() throws InterruptedException { @Override public void release() { - release(locks); + release(this.locks); } private void release(List acquiredLocks) { @@ -62,20 +78,34 @@ private void release(List acquiredLocks) { } } + @Override + public boolean isExclusive() { + return exclusive; + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("resources", resources) // + .toString(); + } + private class CompositeLockManagedBlocker implements ForkJoinPool.ManagedBlocker { - private boolean acquired; + private volatile boolean acquired; @Override public boolean block() throws InterruptedException { - acquireAllLocks(); - acquired = true; + if (!this.acquired) { + acquireAllLocks(); + this.acquired = true; + } return true; } @Override public boolean isReleasable() { - return acquired; + return this.acquired; } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java index cb91d2a84639..c766436ccc98 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java index fc5ac9e257fe..304c9e943f58 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,8 +18,8 @@ import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.ConfigurationParameters; /** @@ -77,7 +77,7 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter Preconditions.condition(maxPoolSizeFactor.compareTo(BigDecimal.ONE) >= 0, () -> String.format( "Factor '%s' specified via configuration parameter '%s' must be greater than or equal to 1", - factor, CONFIG_DYNAMIC_FACTOR_PROPERTY_NAME)); + factor, CONFIG_DYNAMIC_MAX_POOL_SIZE_FACTOR_PROPERTY_NAME)); return maxPoolSizeFactor.multiply(BigDecimal.valueOf(parallelism)).intValue(); }).orElseGet(() -> 256 + parallelism); @@ -99,13 +99,13 @@ public ParallelExecutionConfiguration createConfiguration(ConfigurationParameter public ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters) { String className = configurationParameters.get(CONFIG_CUSTOM_CLASS_PROPERTY_NAME).orElseThrow( () -> new JUnitException(CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " must be set")); - return ReflectionUtils.tryToLoadClass(className) // + return ReflectionSupport.tryToLoadClass(className) // .andThenTry(strategyClass -> { Preconditions.condition( ParallelExecutionConfigurationStrategy.class.isAssignableFrom(strategyClass), CONFIG_CUSTOM_CLASS_PROPERTY_NAME + " does not implement " + ParallelExecutionConfigurationStrategy.class); - return (ParallelExecutionConfigurationStrategy) ReflectionUtils.newInstance(strategyClass); + return (ParallelExecutionConfigurationStrategy) ReflectionSupport.newInstance(strategyClass); }) // .andThenTry(strategy -> strategy.createConfiguration(configurationParameters)) // .getOrThrow(cause -> new JUnitException( diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java index bfc78c472970..4f8ccca9d8be 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/EngineExecutionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java index 4d090c3a69d9..3373f9259f4e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ExclusiveResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,11 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.Comparator.comparing; +import static java.util.Comparator.naturalOrder; import static org.apiguardian.api.API.Status.STABLE; +import java.util.Comparator; import java.util.Objects; import java.util.concurrent.locks.ReadWriteLock; @@ -50,6 +53,14 @@ public class ExclusiveResource { static final ExclusiveResource GLOBAL_READ = new ExclusiveResource(GLOBAL_KEY, LockMode.READ); static final ExclusiveResource GLOBAL_READ_WRITE = new ExclusiveResource(GLOBAL_KEY, LockMode.READ_WRITE); + static final Comparator COMPARATOR // + = comparing(ExclusiveResource::getKey, globalKeyFirst().thenComparing(naturalOrder())) // + .thenComparing(ExclusiveResource::getLockMode); + + private static Comparator globalKeyFirst() { + return comparing(key -> !GLOBAL_KEY.equals(key)); + } + private final String key; private final LockMode lockMode; private int hash; diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java index 09afd8df3eb7..9ccb3747680f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorService.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,10 +12,14 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.SAME_THREAD; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Constructor; +import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; import java.util.LinkedList; import java.util.List; @@ -26,7 +30,6 @@ import java.util.concurrent.ForkJoinTask; import java.util.concurrent.ForkJoinWorkerThread; import java.util.concurrent.Future; -import java.util.concurrent.RecursiveAction; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Predicate; @@ -50,8 +53,12 @@ @API(status = STABLE, since = "1.10") public class ForkJoinPoolHierarchicalTestExecutorService implements HierarchicalTestExecutorService { - private final ForkJoinPool forkJoinPool; + // package-private for testing + final ForkJoinPool forkJoinPool; + + private final TaskEventListener taskEventListener; private final int parallelism; + private final ThreadLocal threadLocks = ThreadLocal.withInitial(ThreadLock::new); /** * Create a new {@code ForkJoinPoolHierarchicalTestExecutorService} based on @@ -71,7 +78,13 @@ public ForkJoinPoolHierarchicalTestExecutorService(ConfigurationParameters confi */ @API(status = STABLE, since = "1.10") public ForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration) { + this(configuration, TaskEventListener.NOOP); + } + + ForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration, + TaskEventListener taskEventListener) { forkJoinPool = createForkJoinPool(configuration); + this.taskEventListener = taskEventListener; parallelism = forkJoinPool.getParallelism(); LoggerFactory.getLogger(getClass()).config(() -> "Using ForkJoinPool with parallelism of " + parallelism); } @@ -132,7 +145,7 @@ public Future submit(TestTask testTask) { if (testTask.getExecutionMode() == CONCURRENT && ForkJoinTask.getSurplusQueuedTaskCount() < parallelism) { return exclusiveTask.fork(); } - exclusiveTask.compute(); + exclusiveTask.execSync(); return completedFuture(null); } @@ -143,33 +156,42 @@ private boolean isAlreadyRunningInForkJoinPool() { @Override public void invokeAll(List tasks) { if (tasks.size() == 1) { - new ExclusiveTask(tasks.get(0)).compute(); + new ExclusiveTask(tasks.get(0)).execSync(); return; } - Deque nonConcurrentTasks = new LinkedList<>(); + Deque isolatedTasks = new LinkedList<>(); + Deque sameThreadTasks = new LinkedList<>(); Deque concurrentTasksInReverseOrder = new LinkedList<>(); - forkConcurrentTasks(tasks, nonConcurrentTasks, concurrentTasksInReverseOrder); - executeNonConcurrentTasks(nonConcurrentTasks); + forkConcurrentTasks(tasks, isolatedTasks, sameThreadTasks, concurrentTasksInReverseOrder); + executeSync(sameThreadTasks); joinConcurrentTasksInReverseOrderToEnableWorkStealing(concurrentTasksInReverseOrder); + executeSync(isolatedTasks); } - private void forkConcurrentTasks(List tasks, Deque nonConcurrentTasks, - Deque concurrentTasksInReverseOrder) { + private void forkConcurrentTasks(List tasks, Deque isolatedTasks, + Deque sameThreadTasks, Deque concurrentTasksInReverseOrder) { for (TestTask testTask : tasks) { ExclusiveTask exclusiveTask = new ExclusiveTask(testTask); - if (testTask.getExecutionMode() == CONCURRENT) { - exclusiveTask.fork(); - concurrentTasksInReverseOrder.addFirst(exclusiveTask); + if (requiresGlobalReadWriteLock(testTask)) { + isolatedTasks.add(exclusiveTask); + } + else if (testTask.getExecutionMode() == SAME_THREAD) { + sameThreadTasks.add(exclusiveTask); } else { - nonConcurrentTasks.add(exclusiveTask); + exclusiveTask.fork(); + concurrentTasksInReverseOrder.addFirst(exclusiveTask); } } } - private void executeNonConcurrentTasks(Deque nonConcurrentTasks) { - for (ExclusiveTask task : nonConcurrentTasks) { - task.compute(); + private static boolean requiresGlobalReadWriteLock(TestTask testTask) { + return testTask.getResourceLock().getResources().contains(GLOBAL_READ_WRITE); + } + + private void executeSync(Deque tasks) { + for (ExclusiveTask task : tasks) { + task.execSync(); } } @@ -177,7 +199,18 @@ private void joinConcurrentTasksInReverseOrderToEnableWorkStealing( Deque concurrentTasksInReverseOrder) { for (ExclusiveTask forkedTask : concurrentTasksInReverseOrder) { forkedTask.join(); + resubmitDeferredTasks(); + } + } + + private void resubmitDeferredTasks() { + List deferredTasks = threadLocks.get().deferredTasks; + for (ExclusiveTask deferredTask : deferredTasks) { + if (!deferredTask.isDone()) { + deferredTask.fork(); + } } + deferredTasks.clear(); } @Override @@ -186,8 +219,8 @@ public void close() { } // this class cannot not be serialized because TestTask is not Serializable - @SuppressWarnings("serial") - static class ExclusiveTask extends RecursiveAction { + @SuppressWarnings({ "serial", "RedundantSuppression" }) + class ExclusiveTask extends ForkJoinTask { private final TestTask testTask; @@ -195,17 +228,61 @@ static class ExclusiveTask extends RecursiveAction { this.testTask = testTask; } + /** + * Always returns {@code null}. + * + * @return {@code null} always + */ + public final Void getRawResult() { + return null; + } + + /** + * Requires null completion value. + */ + protected final void setRawResult(Void mustBeNull) { + } + + void execSync() { + boolean completed = exec(); + if (!completed) { + throw new IllegalStateException( + "Task was deferred but should have been executed synchronously: " + testTask); + } + } + @SuppressWarnings("try") @Override - public void compute() { - try (ResourceLock lock = testTask.getResourceLock().acquire()) { + public boolean exec() { + // Check if this task is compatible with the current resource lock, if there is any. + // If not, we put this task in the thread local as a deferred task + // and let the worker thread fork it once it is done with the current task. + ResourceLock resourceLock = testTask.getResourceLock(); + ThreadLock threadLock = threadLocks.get(); + if (!threadLock.areAllHeldLocksCompatibleWith(resourceLock)) { + threadLock.addDeferredTask(this); + taskEventListener.deferred(testTask); + // Return false to indicate that this task is not done yet + // this means that .join() will wait. + return false; + } + try ( // + ResourceLock lock = resourceLock.acquire(); // + @SuppressWarnings("unused") + ThreadLock.NestedResourceLock nested = threadLock.withNesting(lock) // + ) { testTask.execute(); + return true; } catch (InterruptedException e) { throw ExceptionUtils.throwAsUncheckedException(e); } } + @Override + public String toString() { + return "ExclusiveTask [" + testTask + "]"; + } } static class WorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory { @@ -228,4 +305,35 @@ static class WorkerThread extends ForkJoinWorkerThread { } + static class ThreadLock { + private final Deque locks = new ArrayDeque<>(2); + private final List deferredTasks = new ArrayList<>(); + + void addDeferredTask(ExclusiveTask task) { + deferredTasks.add(task); + } + + NestedResourceLock withNesting(ResourceLock lock) { + locks.push(lock); + return locks::pop; + } + + boolean areAllHeldLocksCompatibleWith(ResourceLock lock) { + return locks.stream().allMatch(l -> l.isCompatible(lock)); + } + + interface NestedResourceLock extends AutoCloseable { + @Override + void close(); + } + } + + interface TaskEventListener { + + TaskEventListener NOOP = __ -> { + }; + + void deferred(TestTask testTask); + } + } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java index c80aa81aeed3..92cb86129952 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java index 9f5da42f82a3..3ce7e541cf43 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java index e69b5dc30330..d879aa2b5978 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorService.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java index 223f7873525d..988d52efb69e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/LockManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,16 +10,17 @@ package org.junit.platform.engine.support.hierarchical; -import static java.util.Comparator.comparing; -import static java.util.Comparator.naturalOrder; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; -import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; +import static org.junit.platform.commons.util.CollectionUtils.toUnmodifiableList; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode.READ; import java.util.Collection; -import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -33,56 +34,70 @@ */ class LockManager { - private static final Comparator COMPARATOR // - = comparing(ExclusiveResource::getKey, globalKeyFirst().thenComparing(naturalOrder())) // - .thenComparing(ExclusiveResource::getLockMode); + private final Map locksByKey = new ConcurrentHashMap<>(); + private final SingleLock globalReadLock; + private final SingleLock globalReadWriteLock; - private static Comparator globalKeyFirst() { - return comparing(key -> !GLOBAL_KEY.equals(key)); + public LockManager() { + globalReadLock = new SingleLock(GLOBAL_READ, toLock(GLOBAL_READ)); + globalReadWriteLock = new SingleLock(GLOBAL_READ_WRITE, toLock(GLOBAL_READ_WRITE)); } - private final Map locksByKey = new ConcurrentHashMap<>(); - ResourceLock getLockForResources(Collection resources) { - if (resources.size() == 1) { - return getLockForResource(getOnlyElement(resources)); - } - List locks = getDistinctSortedLocks(resources); - return toResourceLock(locks); + return toResourceLock(toDistinctSortedResources(resources)); } ResourceLock getLockForResource(ExclusiveResource resource) { - return new SingleLock(toLock(resource)); + return toResourceLock(singletonList(resource)); } - private List getDistinctSortedLocks(Collection resources) { + private List toDistinctSortedResources(Collection resources) { + if (resources.isEmpty()) { + return emptyList(); + } + if (resources.size() == 1) { + return singletonList(getOnlyElement(resources)); + } // @formatter:off Map> resourcesByKey = resources.stream() - .sorted(COMPARATOR) + .sorted(ExclusiveResource.COMPARATOR) .distinct() .collect(groupingBy(ExclusiveResource::getKey, LinkedHashMap::new, toList())); return resourcesByKey.values().stream() .map(resourcesWithSameKey -> resourcesWithSameKey.get(0)) - .map(this::toLock) - .collect(toList()); + .collect(toUnmodifiableList()); // @formatter:on } - private Lock toLock(ExclusiveResource resource) { - ReadWriteLock lock = this.locksByKey.computeIfAbsent(resource.getKey(), key -> new ReentrantReadWriteLock()); - return resource.getLockMode() == READ ? lock.readLock() : lock.writeLock(); - } - - private ResourceLock toResourceLock(List locks) { - switch (locks.size()) { + private ResourceLock toResourceLock(List resources) { + switch (resources.size()) { case 0: return NopLock.INSTANCE; case 1: - return new SingleLock(locks.get(0)); + return toSingleLock(getOnlyElement(resources)); default: - return new CompositeLock(locks); + return new CompositeLock(resources, toLocks(resources)); + } + } + + private SingleLock toSingleLock(ExclusiveResource resource) { + if (GLOBAL_READ.equals(resource)) { + return globalReadLock; + } + if (GLOBAL_READ_WRITE.equals(resource)) { + return globalReadWriteLock; } + return new SingleLock(resource, toLock(resource)); + } + + private List toLocks(List resources) { + return resources.stream().map(this::toLock).collect(toUnmodifiableList()); + } + + private Lock toLock(ExclusiveResource resource) { + ReadWriteLock lock = this.locksByKey.computeIfAbsent(resource.getKey(), key -> new ReentrantReadWriteLock()); + return resource.getLockMode() == READ ? lock.readLock() : lock.writeLock(); } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java index 1726ae2e1965..f2616e60a6cb 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/Node.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java index c3863a049342..050cd065163f 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeExecutionAdvisor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -33,6 +33,10 @@ void useResourceLock(TestDescriptor testDescriptor, ResourceLock resourceLock) { resourceLocksByTestDescriptor.put(testDescriptor, resourceLock); } + void removeResourceLock(TestDescriptor testDescriptor) { + resourceLocksByTestDescriptor.remove(testDescriptor); + } + Optional getForcedExecutionMode(TestDescriptor testDescriptor) { return testDescriptor.getParent().flatMap(this::lookupExecutionModeForcedByAncestor); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java index e4d2208f2bc3..39011dab7162 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -79,6 +79,11 @@ public ExecutionMode getExecutionMode() { return taskContext.getExecutionAdvisor().getForcedExecutionMode(testDescriptor).orElse(node.getExecutionMode()); } + @Override + public String toString() { + return "NodeTestTask [" + testDescriptor + "]"; + } + void setParentContext(C parentContext) { this.parentContext = parentContext; } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java index 6b1e6dfd8852..a6298847d59a 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTestTaskContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java index c972b0565e77..1f95c6233b5b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalker.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -50,9 +50,17 @@ NodeExecutionAdvisor walk(TestDescriptor rootDescriptor) { private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescriptor, NodeExecutionAdvisor advisor) { + + if (advisor.getResourceLock(globalLockDescriptor) == globalReadWriteLock) { + // Global read-write lock is already being enforced, so no additional locks are needed + return; + } + Set exclusiveResources = getExclusiveResources(testDescriptor); if (exclusiveResources.isEmpty()) { - advisor.useResourceLock(testDescriptor, globalReadLock); + if (globalLockDescriptor.equals(testDescriptor)) { + advisor.useResourceLock(globalLockDescriptor, globalReadLock); + } testDescriptor.getChildren().forEach(child -> walk(globalLockDescriptor, child, advisor)); } else { @@ -70,14 +78,24 @@ private void walk(TestDescriptor globalLockDescriptor, TestDescriptor testDescri advisor.forceDescendantExecutionMode(child, SAME_THREAD); }); } - if (!globalLockDescriptor.equals(testDescriptor) && allResources.contains(GLOBAL_READ_WRITE)) { - forceDescendantExecutionModeRecursively(advisor, globalLockDescriptor); + if (allResources.contains(GLOBAL_READ_WRITE)) { + advisor.forceDescendantExecutionMode(globalLockDescriptor, SAME_THREAD); + doForChildrenRecursively(globalLockDescriptor, child -> { + advisor.forceDescendantExecutionMode(child, SAME_THREAD); + // Remove any locks that may have been set for siblings or their descendants + advisor.removeResourceLock(child); + }); advisor.useResourceLock(globalLockDescriptor, globalReadWriteLock); } - if (globalLockDescriptor.equals(testDescriptor) && !allResources.contains(GLOBAL_READ_WRITE)) { - allResources.add(GLOBAL_READ); + else { + if (globalLockDescriptor.equals(testDescriptor)) { + allResources.add(GLOBAL_READ); + } + else { + allResources.remove(GLOBAL_READ); + } + advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources)); } - advisor.useResourceLock(testDescriptor, lockManager.getLockForResources(allResources)); } } @@ -87,8 +105,7 @@ private void forceDescendantExecutionModeRecursively(NodeExecutionAdvisor adviso } private boolean isReadOnly(Set exclusiveResources) { - return exclusiveResources.stream().allMatch( - exclusiveResource -> exclusiveResource.getLockMode() == ExclusiveResource.LockMode.READ); + return exclusiveResources.stream().allMatch(it -> it.getLockMode() == ExclusiveResource.LockMode.READ); } private Set getExclusiveResources(TestDescriptor testDescriptor) { diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java index b2807378a05d..4f916837c32c 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NodeUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java index 6c885ecb0403..ea43494c2b57 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/NopLock.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,12 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.Collections.emptyList; + +import java.util.List; + +import org.junit.platform.commons.util.ToStringBuilder; + /** * No-op {@link ResourceLock} implementation. * @@ -22,6 +28,11 @@ class NopLock implements ResourceLock { private NopLock() { } + @Override + public List getResources() { + return emptyList(); + } + @Override public ResourceLock acquire() { return this; @@ -32,4 +43,13 @@ public void release() { // nothing to do } + @Override + public boolean isExclusive() { + return false; + } + + @Override + public String toString() { + return new ToStringBuilder(this).toString(); + } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java index 52f8e2f0ea1e..d925e0bf384a 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/OpenTest4JAwareThrowableCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java index adde417cbdd3..15f9a3184e7e 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java index 549ce71c694b..68760af8b982 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionConfigurationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java index 1dcdb5601881..4f62637aa6ef 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ResourceLock.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,9 @@ import static org.apiguardian.api.API.Status.STABLE; +import java.util.List; +import java.util.Optional; + import org.apiguardian.api.API; /** @@ -43,4 +46,50 @@ default void close() { release(); } + /** + * {@return the exclusive resources this lock represents} + */ + List getResources(); + + /** + * {@return whether this lock requires exclusiveness} + */ + boolean isExclusive(); + + /** + * {@return whether the given lock is compatible with this lock} + * @param other the other lock to check for compatibility + */ + default boolean isCompatible(ResourceLock other) { + + List ownResources = this.getResources(); + List otherResources = other.getResources(); + + if (ownResources.isEmpty() || otherResources.isEmpty()) { + return true; + } + + // Whenever there's a READ_WRITE lock, it's incompatible with any other lock + // because we guarantee that all children will have exclusive access to the + // resource in question. In practice, whenever a READ_WRITE lock is present, + // NodeTreeWalker will force all children to run in the same thread so that + // it should never attempt to steal work from another thread, and we shouldn't + // actually reach this point. + // The global read lock (which is always on direct children of the engine node) + // needs special treatment so that it is compatible with the first write lock + // (which may be on a test method). + boolean isGlobalReadLock = ownResources.size() == 1 + && ExclusiveResource.GLOBAL_READ.equals(ownResources.get(0)); + if ((!isGlobalReadLock && other.isExclusive()) || this.isExclusive()) { + return false; + } + + Optional potentiallyDeadlockCausingAdditionalResource = otherResources.stream() // + .filter(resource -> !ownResources.contains(resource)) // + .findFirst() // + .filter(resource -> ExclusiveResource.COMPARATOR.compare(resource, + ownResources.get(ownResources.size() - 1)) < 0); + + return !(potentiallyDeadlockCausingAdditionalResource.isPresent()); + } } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java index f18652d5d4bc..d6293ed7874b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SameThreadHierarchicalTestExecutorService.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java index 23840449bd63..bd5358bfab08 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleLock.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,23 +10,36 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.Collections.singletonList; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; + +import java.util.List; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.locks.Lock; +import org.junit.platform.commons.util.ToStringBuilder; + /** * @since 1.3 */ class SingleLock implements ResourceLock { + private final List resources; private final Lock lock; - SingleLock(Lock lock) { + SingleLock(ExclusiveResource resource, Lock lock) { + this.resources = singletonList(resource); this.lock = lock; } + @Override + public List getResources() { + return resources; + } + // for tests only Lock getLock() { - return lock; + return this.lock; } @Override @@ -37,25 +50,38 @@ public ResourceLock acquire() throws InterruptedException { @Override public void release() { - lock.unlock(); + this.lock.unlock(); + } + + @Override + public boolean isExclusive() { + return resources.get(0).getLockMode() == ExclusiveResource.LockMode.READ_WRITE; + } + + @Override + public String toString() { + return new ToStringBuilder(this) // + .append("resource", getOnlyElement(resources)) // + .toString(); } private class SingleLockManagedBlocker implements ForkJoinPool.ManagedBlocker { - private boolean acquired; + private volatile boolean acquired; @Override public boolean block() throws InterruptedException { - lock.lockInterruptibly(); - acquired = true; + if (!this.acquired) { + SingleLock.this.lock.lockInterruptibly(); + this.acquired = true; + } return true; } @Override public boolean isReleasable() { - return acquired || lock.tryLock(); + return this.acquired || (this.acquired = SingleLock.this.lock.tryLock()); } } - } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java index 7765d4b5ed92..3aa0ab4aea6b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java index e8187134671a..88e83cdb9ed5 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/hierarchical/ThrowableCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java index 965eb9b24439..9167dde42b13 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStore.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -139,7 +139,6 @@ public void close() { * closed */ public Object get(N namespace, Object key) { - rejectIfClosed(); StoredValue storedValue = getStoredValue(new CompositeKey<>(namespace, key)); return StoredValue.evaluateIfNotNull(storedValue); } @@ -156,7 +155,6 @@ public Object get(N namespace, Object key) { * be cast to the required type, or if this store has already been closed */ public T get(N namespace, Object key, Class requiredType) throws NamespacedHierarchicalStoreException { - rejectIfClosed(); Object value = get(namespace, key); return castToRequiredType(key, value, requiredType); } @@ -174,13 +172,15 @@ public T get(N namespace, Object key, Class requiredType) throws Namespac * closed */ public Object getOrComputeIfAbsent(N namespace, K key, Function defaultCreator) { - rejectIfClosed(); Preconditions.notNull(defaultCreator, "defaultCreator must not be null"); CompositeKey compositeKey = new CompositeKey<>(namespace, key); StoredValue storedValue = getStoredValue(compositeKey); if (storedValue == null) { storedValue = this.storedValues.computeIfAbsent(compositeKey, - __ -> storedValue(new MemoizingSupplier(() -> defaultCreator.apply(key)))); + __ -> storedValue(new MemoizingSupplier(() -> { + rejectIfClosed(); + return defaultCreator.apply(key); + }))); } return storedValue.evaluate(); } @@ -202,7 +202,6 @@ public Object getOrComputeIfAbsent(N namespace, K key, Function def public V getOrComputeIfAbsent(N namespace, K key, Function defaultCreator, Class requiredType) throws NamespacedHierarchicalStoreException { - rejectIfClosed(); Object value = getOrComputeIfAbsent(namespace, key, defaultCreator); return castToRequiredType(key, value, requiredType); } diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreException.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreException.java index bf9dfefa1d16..2e67f3d203f4 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreException.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java b/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java index baabf4794d85..d595f8bdcd1b 100644 --- a/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java +++ b/junit-platform-engine/src/module/org.junit.platform.engine/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 @@ -11,8 +11,8 @@ /** * Public API for test engines. * - *

Provides the {@linkplain org.junit.platform.engine.TestEngine} interface, test discovery - * and execution reporting support. + *

Provides the {@link org.junit.platform.engine.TestEngine} interface, + * test discovery, and execution reporting support. * * @since 1.0 */ diff --git a/junit-platform-engine/src/nativeImage/initialize-at-build-time b/junit-platform-engine/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..5b1168bb74a6 --- /dev/null +++ b/junit-platform-engine/src/nativeImage/initialize-at-build-time @@ -0,0 +1,7 @@ +org.junit.platform.engine.TestDescriptor$Type +org.junit.platform.engine.UniqueId +org.junit.platform.engine.UniqueId$Segment +org.junit.platform.engine.UniqueIdFormat +org.junit.platform.engine.support.descriptor.ClassSource +org.junit.platform.engine.support.descriptor.MethodSource +org.junit.platform.engine.support.hierarchical.Node$ExecutionMode diff --git a/junit-platform-engine/src/test/README.md b/junit-platform-engine/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-engine/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java index e6cf952390a2..2bb72762e96d 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoEngineExecutionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java index 0361e8c9dcb9..00ead8e16784 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalContainerDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java index 8da648cc3249..5dd40c886bbb 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalEngineDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java index aaaf64b540de..29b57733512a 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java index 61656c6b476e..ff5e32299dde 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/engine/support/hierarchical/DemoHierarchicalTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java index 1336811dfac7..a6e7a36fa975 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestDescriptorStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java index 0c1dc37aa026..7754c75f411b 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineSpy.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java index d97cb8307fd2..d29859313222 100644 --- a/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java +++ b/junit-platform-engine/src/testFixtures/java/org/junit/platform/fakes/TestEngineStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java index 100942573680..f36d31cd2ff3 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java index cef4753f01b2..a854b81d98ec 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/FlightRecordingExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -26,6 +26,7 @@ import org.apiguardian.api.API; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -97,6 +98,14 @@ public void reportingEntryPublished(TestIdentifier test, ReportEntry reportEntry } } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + FileEntryEvent event = new FileEntryEvent(); + event.uniqueId = testIdentifier.getUniqueId(); + event.path = file.getPath().toAbsolutePath().toString(); + event.commit(); + } + @Category({ "JUnit", "Execution" }) @StackTrace(false) abstract static class ExecutionEvent extends Event { @@ -159,4 +168,14 @@ static class ReportEntryEvent extends ExecutionEvent { @Label("Value") String value; } + + @Label("File Entry") + @Name("org.junit.FileEntry") + static class FileEntryEvent extends ExecutionEvent { + @UniqueId + @Label("Unique Id") + String uniqueId; + @Label("Path") + String path; + } } diff --git a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java index 9083cef2e7b2..21ced50f0e42 100644 --- a/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java +++ b/junit-platform-jfr/src/main/java/org/junit/platform/jfr/UniqueId.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java b/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java index 61aad3ec42a6..6f38bf063cf7 100644 --- a/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java +++ b/junit-platform-jfr/src/module/org.junit.platform.jfr/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-jfr/src/test/README.md b/junit-platform-jfr/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-jfr/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-launcher/junit-platform-launcher.gradle.kts b/junit-platform-launcher/junit-platform-launcher.gradle.kts index a9b3630762c8..acda6a79436f 100644 --- a/junit-platform-launcher/junit-platform-launcher.gradle.kts +++ b/junit-platform-launcher/junit-platform-launcher.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.native-image-properties") `java-test-fixtures` } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java new file mode 100644 index 000000000000..9ac17deea90c --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/AbstractMethodFilter.java @@ -0,0 +1,64 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.support.descriptor.MethodSource; + +/** + * Abstract {@link MethodFilter} that servers as a superclass + * for filters including or excluding fully qualified method names + * without parameters based on pattern-matching. + * + * @since 1.12 + */ +abstract class AbstractMethodFilter implements MethodFilter { + + protected final List patterns; + protected final String patternDescription; + + AbstractMethodFilter(String... patterns) { + Preconditions.notEmpty(patterns, "patterns array must not be null or empty"); + Preconditions.containsNoNullElements(patterns, "patterns array must not contain null elements"); + this.patterns = Arrays.stream(patterns).map(Pattern::compile).collect(toList()); + this.patternDescription = Arrays.stream(patterns).collect(joining("' OR '", "'", "'")); + } + + protected Optional findMatchingPattern(String methodName) { + if (methodName == null) { + return Optional.empty(); + } + return this.patterns.stream().filter(pattern -> pattern.matcher(methodName).matches()).findAny(); + } + + protected String getFullyQualifiedMethodNameFromDescriptor(TestDescriptor descriptor) { + return descriptor.getSource() // + .filter(source -> source instanceof MethodSource) // + .map(methodSource -> getFullyQualifiedMethodNameWithoutParameters(((MethodSource) methodSource))) // + .orElse(null); + } + + private String getFullyQualifiedMethodNameWithoutParameters(MethodSource methodSource) { + String methodNameWithParentheses = ReflectionUtils.getFullyQualifiedMethodName(methodSource.getJavaClass(), + methodSource.getMethodName(), (Class[]) null); + return methodNameWithParentheses.substring(0, methodNameWithParentheses.length() - 2); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java index d12ed056f1b3..b3c104b57fa2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineDiscoveryResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -100,11 +100,11 @@ public Optional getThrowable() { @Override public String toString() { // @formatter:off - return new ToStringBuilder(this) - .append("status", status) - .append("throwable", throwable) - .toString(); - // @formatter:on + return new ToStringBuilder(this) + .append("status", status) + .append("throwable", throwable) + .toString(); + // @formatter:on } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java index a65a68f265db..8dd34af794aa 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/EngineFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java new file mode 100644 index 000000000000..b9b46d764a2e --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/ExcludeMethodFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.regex.Pattern; + +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; + +/** + * {@link MethodFilter} that matches fully qualified method names against + * patterns in the form of regular expressions. + * + *

If the fully qualified name of a method matches against at least one + * pattern, the class will be excluded. + * + * @since 1.12 + */ +class ExcludeMethodFilter extends AbstractMethodFilter { + + ExcludeMethodFilter(String... patterns) { + super(patterns); + } + + @Override + public FilterResult apply(TestDescriptor descriptor) { + String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor); + return findMatchingPattern(methodName) // + .map(pattern -> excluded(formatExclusionReason(methodName, pattern))) // + .orElseGet(() -> included(formatInclusionReason(methodName))); + } + + private String formatInclusionReason(String methodName) { + return String.format("Method name [%s] does not match any excluded pattern: %s", methodName, + patternDescription); + } + + private String formatExclusionReason(String methodName, Pattern pattern) { + return String.format("Method name [%s] matches excluded pattern: '%s'", methodName, pattern); + } + + @Override + public String toString() { + return String.format("%s that excludes method names that match one of the following regular expressions: %s", + getClass().getSimpleName(), patternDescription); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java new file mode 100644 index 000000000000..30d943bfcf94 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/IncludeMethodFilter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.junit.platform.engine.FilterResult.excluded; +import static org.junit.platform.engine.FilterResult.included; + +import java.util.regex.Pattern; + +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; + +/** + * {@link MethodFilter} that matches fully qualified method names against + * patterns in the form of regular expressions. + * + *

If the fully qualified name of a method matches against at least one + * pattern, the method will be included. + * + * @since 1.12 + */ +class IncludeMethodFilter extends AbstractMethodFilter { + + IncludeMethodFilter(String... patterns) { + super(patterns); + } + + @Override + public FilterResult apply(TestDescriptor descriptor) { + String methodName = getFullyQualifiedMethodNameFromDescriptor(descriptor); + return findMatchingPattern(methodName) // + .map(pattern -> included(formatInclusionReason(methodName, pattern))) // + .orElseGet(() -> excluded(formatExclusionReason(methodName))); + } + + private String formatInclusionReason(String methodName, Pattern pattern) { + return String.format("Method name [%s] matches included pattern: '%s'", methodName, pattern); + } + + private String formatExclusionReason(String methodName) { + return String.format("Method name [%s] does not match any included pattern: %s", methodName, + patternDescription); + } + + @Override + public String toString() { + return String.format("%s that includes method names that match one of the following regular expressions: %s", + getClass().getSimpleName(), patternDescription); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java index 41e5f0960576..75a5f07deabf 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -36,7 +36,7 @@ * {@linkplain #registerTestExecutionListeners register} one or more * {@link TestExecutionListener} instances in order to get feedback about the * progress and results of test execution. Listeners will be notified of events - * in the order in which they were registered. The default implementation + * in the order in which they were registered. The default implementation * returned by {@link org.junit.platform.launcher.core.LauncherFactory#create} * dynamically discovers test execution listeners via Java's * {@link java.util.ServiceLoader ServiceLoader} mechanism. 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 da4d81625f6d..a2af5d187a58 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -92,8 +92,9 @@ public class LauncherConstants { public static final String STDERR_REPORT_ENTRY_KEY = "stderr"; /** - * Property name used to provide patterns for deactivating listeners registered - * via the {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value} + * Property name used to provide patterns for deactivating + * {@linkplain TestExecutionListener listeners} registered via the + * {@link java.util.ServiceLoader ServiceLoader} mechanism: {@value} * *

Pattern Matching Syntax

* @@ -121,6 +122,17 @@ public class LauncherConstants { * {@code org.example.TheirListener}. * * + *

Only listeners registered via the {@code ServiceLoader} mechanism can + * be deactivated. In other words, any listener registered explicitly via the + * {@link LauncherDiscoveryRequest} cannot be deactivated via this + * configuration parameter. + * + *

In addition, since execution listeners are registered before the test + * run starts, this configuration parameter can only be supplied as a JVM + * system property or via the JUnit Platform configuration file but cannot + * be supplied in the {@link LauncherDiscoveryRequest}} that is passed to + * the {@link Launcher}. + * * @see #DEACTIVATE_ALL_LISTENERS_PATTERN * @see org.junit.platform.launcher.TestExecutionListener */ @@ -134,7 +146,7 @@ public class LauncherConstants { * @see #DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME * @see org.junit.platform.launcher.TestExecutionListener */ - public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.DEACTIVATE_ALL_PATTERN; + public static final String DEACTIVATE_ALL_LISTENERS_PATTERN = ClassNamePatternFilterUtils.ALL_PATTERN; /** * Property name used to enable support for @@ -143,6 +155,11 @@ public class LauncherConstants { * *

By default, interceptor registration is disabled. * + *

Since interceptors are registered before the test run starts, this + * configuration parameter can only be supplied as a JVM system property or + * via the JUnit Platform configuration file but cannot be supplied in the + * {@link LauncherDiscoveryRequest}} that is passed to the {@link Launcher}. + * * @see LauncherInterceptor */ @API(status = EXPERIMENTAL, since = "1.10") @@ -174,6 +191,34 @@ public class LauncherConstants { @API(status = EXPERIMENTAL, since = "1.10") public static final String STACKTRACE_PRUNING_ENABLED_PROPERTY_NAME = "junit.platform.stacktrace.pruning.enabled"; + /** + * Property name used to configure the output directory for reporting. + * + *

If set, value must be a valid path that will be created if it doesn't + * exist. If not set, the default output directory will be determined by the + * reporting engine based on the current working directory. + * + * @since 1.12 + * @see #OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER + * @see org.junit.platform.engine.reporting.OutputDirectoryProvider + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; + + /** + * Placeholder for use in {@link #OUTPUT_DIR_PROPERTY_NAME} that will be + * replaced with a unique number. + * + *

This can be used to create a unique output directory for each test + * run. For example, if multiple forks are used, each fork can be configured + * to write its output to a separate directory. + * + * @since 1.12 + * @see #OUTPUT_DIR_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static final String OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER = "{uniqueNumber}"; + private LauncherConstants() { /* no-op */ } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java index 25c8751b6ef2..6b44f026b70f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java index f6043705a82c..058281b9b375 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherDiscoveryRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java index 8375db13beed..490ac65483a2 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java index 92fb5e9f37ae..c78ed7761888 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java index 1024b695c188..56b2312d612f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/LauncherSessionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/MethodFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/MethodFilter.java new file mode 100644 index 000000000000..cff14852ed79 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/MethodFilter.java @@ -0,0 +1,107 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.reflect.Method; +import java.util.List; + +import org.apiguardian.api.API; + +/** + * {@link PostDiscoveryFilter} that is applied to the fully qualified + * {@link Method} name without parameters. + * + * @since 1.12 + * @see #includeMethodNamePatterns(String...) + * @see #excludeMethodNamePatterns(String...) + */ +@API(status = EXPERIMENTAL, since = "1.12") +public interface MethodFilter extends PostDiscoveryFilter { + + /** + * Create a new include {@link MethodFilter} based on the + * supplied patterns. + * + *

The patterns are combined using OR semantics, i.e. if the fully + * qualified name of a method matches against at least one of the patterns, + * the method will be included in the result set. + * + * @param patterns regular expressions to match against fully qualified + * method names; never {@code null}, empty, or containing {@code null} + * @see Class#getName() + * @see Method#getName() + * @see #includeMethodNamePatterns(List) + * @see #excludeMethodNamePatterns(String...) + */ + static MethodFilter includeMethodNamePatterns(String... patterns) { + return new IncludeMethodFilter(patterns); + } + + /** + * Create a new include {@link MethodFilter} based on the + * supplied patterns. + * + *

The patterns are combined using OR semantics, i.e. if the fully + * qualified name of a method matches against at least one of the patterns, + * the method will be included in the result set. + * + * @param patterns regular expressions to match against fully qualified + * method names; never {@code null}, empty, or containing {@code null} + * @see Class#getName() + * @see Method#getName() + * @see #includeMethodNamePatterns(String...) + * @see #excludeMethodNamePatterns(String...) + */ + static MethodFilter includeMethodNamePatterns(List patterns) { + return includeMethodNamePatterns(patterns.toArray(new String[0])); + } + + /** + * Create a new exclude {@link MethodFilter} based on the + * supplied patterns. + * + *

The patterns are combined using OR semantics, i.e. if the fully + * qualified name of a method matches against at least one of the patterns, + * the method will be excluded from the result set. + * + * @param patterns regular expressions to match against fully qualified + * method names; never {@code null}, empty, or containing {@code null} + * @see Class#getName() + * @see Method#getName() + * @see #excludeMethodNamePatterns(List) + * @see #includeMethodNamePatterns(String...) + */ + static MethodFilter excludeMethodNamePatterns(String... patterns) { + return new ExcludeMethodFilter(patterns); + } + + /** + * Create a new exclude {@link MethodFilter} based on the + * supplied patterns. + * + *

The patterns are combined using OR semantics, i.e. if the fully + * qualified name of a method matches against at least one of the patterns, + * the method will be excluded from the result set. + * + * @param patterns regular expressions to match against fully qualified + * method names; never {@code null}, empty, or containing {@code null} + * @see Class#getName() + * @see Method#getName() + * @see #excludeMethodNamePatterns(String...) + * @see #includeMethodNamePatterns(String...) + */ + static MethodFilter excludeMethodNamePatterns(List patterns) { + return excludeMethodNamePatterns(patterns.toArray(new String[0])); + } + +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java index c6a6911f478a..d0ae9550024e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/PostDiscoveryFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java index 7b3579af26a3..2de149079747 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TagFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java index 9179705f3661..10f6327a4a76 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,12 +10,14 @@ package org.junit.platform.launcher; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import org.apiguardian.api.API; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -184,4 +186,16 @@ default void executionFinished(TestIdentifier testIdentifier, TestExecutionResul default void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry entry) { } + /** + * Called when a file or directory has been published for the supplied + * {@link TestIdentifier}. + * + *

Can be called at any time during the execution of a test plan. + * + * @param testIdentifier describes the test or container to which the entry pertains + * @param file the published {@code FileEntry} + */ + @API(status = EXPERIMENTAL, since = "1.12") + default void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java index ae3cd416469e..f397eb0b9bc0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java index c83f11124913..ca8361bb3757 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/TestPlan.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -34,6 +34,7 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * {@code TestPlan} describes the tree of tests and containers as discovered @@ -70,6 +71,7 @@ public class TestPlan { private final boolean containsTests; private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; /** * Construct a new {@code TestPlan} from the supplied collection of @@ -82,24 +84,28 @@ public class TestPlan { * plan should be created; never {@code null} * @param configurationParameters the {@code ConfigurationParameters} for * this test plan; never {@code null} + * @param outputDirectoryProvider the {@code OutputDirectoryProvider} for + * this test plan; never {@code null} * @return a new test plan */ - @API(status = INTERNAL, since = "1.0") + @API(status = INTERNAL, since = "1.12") public static TestPlan from(Collection engineDescriptors, - ConfigurationParameters configurationParameters) { + ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { Preconditions.notNull(engineDescriptors, "Cannot create TestPlan from a null collection of TestDescriptors"); Preconditions.notNull(configurationParameters, "Cannot create TestPlan from null ConfigurationParameters"); TestPlan testPlan = new TestPlan(engineDescriptors.stream().anyMatch(TestDescriptor::containsTests), - configurationParameters); + configurationParameters, outputDirectoryProvider); TestDescriptor.Visitor visitor = descriptor -> testPlan.addInternal(TestIdentifier.from(descriptor)); engineDescriptors.forEach(engineDescriptor -> engineDescriptor.accept(visitor)); return testPlan; } @API(status = INTERNAL, since = "1.4") - protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters) { + protected TestPlan(boolean containsTests, ConfigurationParameters configurationParameters, + OutputDirectoryProvider outputDirectoryProvider) { this.containsTests = containsTests; this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; } /** @@ -291,6 +297,17 @@ public ConfigurationParameters getConfigurationParameters() { return this.configurationParameters; } + /** + * Get the {@link OutputDirectoryProvider} for this test plan. + * + * @return the output directory provider; never {@code null} + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public OutputDirectoryProvider getOutputDirectoryProvider() { + return this.outputDirectoryProvider; + } + /** * Accept the supplied {@link Visitor} for a depth-first traversal of the * test plan. diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java new file mode 100644 index 000000000000..3b95f7153e6a --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentChecker.java @@ -0,0 +1,110 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static java.util.Collections.unmodifiableList; +import static java.util.Comparator.comparing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.ClassLoaderUtils; + +/** + * @since 1.12 + */ +class ClasspathAlignmentChecker { + + // VisibleForTesting + static final List WELL_KNOWN_PACKAGES = unmodifiableList(Arrays.asList( // + "org.junit.jupiter.api", // + "org.junit.jupiter.engine", // + "org.junit.jupiter.migrationsupport", // + "org.junit.jupiter.params", // + "org.junit.platform.commons", // + "org.junit.platform.console", // + "org.junit.platform.engine", // + "org.junit.platform.jfr", // + "org.junit.platform.launcher", // + "org.junit.platform.reporting", // + "org.junit.platform.runner", // + "org.junit.platform.suite.api", // + "org.junit.platform.suite.commons", // + "org.junit.platform.suite.engine", // + "org.junit.platform.testkit", // + "org.junit.vintage.engine" // + )); + + static Optional check(LinkageError error) { + ClassLoader classLoader = ClassLoaderUtils.getClassLoader(ClasspathAlignmentChecker.class); + Function packageLookup = name -> ReflectionSupport.findMethod(ClassLoader.class, + "getDefinedPackage", String.class) // + .map(m -> (Package) ReflectionSupport.invokeMethod(m, classLoader, name)) // + .orElseGet(() -> getPackage(name)); + return check(error, packageLookup); + } + + // VisibleForTesting + static Optional check(LinkageError error, Function packageLookup) { + Map> packagesByVersions = new HashMap<>(); + WELL_KNOWN_PACKAGES.stream() // + .map(packageLookup) // + .filter(Objects::nonNull) // + .forEach(pkg -> { + String version = pkg.getImplementationVersion(); + if (version != null) { + if (pkg.getName().startsWith("org.junit.platform") && version.contains(".")) { + version = platformToJupiterVersion(version); + } + packagesByVersions.computeIfAbsent(version, __ -> new ArrayList<>()).add(pkg); + } + }); + if (packagesByVersions.size() > 1) { + StringBuilder message = new StringBuilder(); + String lineBreak = System.lineSeparator(); + message.append("The wrapped ").append(error.getClass().getSimpleName()) // + .append(" is likely caused by the versions of JUnit jars on the classpath/module path ") // + .append("not being properly aligned. ") // + .append(lineBreak) // + .append("Please ensure consistent versions are used (see https://junit.org/junit5/docs/") // + .append(platformToJupiterVersion( + ClasspathAlignmentChecker.class.getPackage().getImplementationVersion())) // + .append("/user-guide/#dependency-metadata).") // + .append(lineBreak) // + .append("The following conflicting versions were detected:").append(lineBreak); + packagesByVersions.values().stream() // + .flatMap(List::stream) // + .sorted(comparing(Package::getName)) // + .map(pkg -> String.format("- %s: %s%n", pkg.getName(), pkg.getImplementationVersion())) // + .forEach(message::append); + return Optional.of(new JUnitException(message.toString(), error)); + } + return Optional.empty(); + } + + private static String platformToJupiterVersion(String version) { + int majorVersion = Integer.parseInt(version.substring(0, version.indexOf("."))) + 4; + return majorVersion + version.substring(version.indexOf(".")); + } + + @SuppressWarnings({ "deprecation", "RedundantSuppression" }) // only called when running on JDK 8 + private static Package getPackage(String name) { + return Package.getPackage(name); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckingLauncherInterceptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckingLauncherInterceptor.java new file mode 100644 index 000000000000..4bef73ba35d7 --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckingLauncherInterceptor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.util.Optional; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.launcher.LauncherInterceptor; + +class ClasspathAlignmentCheckingLauncherInterceptor implements LauncherInterceptor { + + static final LauncherInterceptor INSTANCE = new ClasspathAlignmentCheckingLauncherInterceptor(); + + @Override + public T intercept(Invocation invocation) { + try { + return invocation.proceed(); + } + catch (LinkageError e) { + Optional exception = ClasspathAlignmentChecker.check(e); + if (exception.isPresent()) { + throw exception.get(); + } + throw e; + } + } + + @Override + public void close() { + // do nothing + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java index b68e59bee23c..dac24275f15f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeEngineExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,6 +21,7 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; class CompositeEngineExecutionListener implements EngineExecutionListener { @@ -67,6 +68,13 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e () -> "reportingEntryPublished(" + testDescriptor + ", " + entry + ")"); } + @Override + public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + notifyEach(engineExecutionListeners, IterationOrder.ORIGINAL, + listener -> listener.fileEntryPublished(testDescriptor, file), + () -> "fileEntryPublished(" + testDescriptor + ", " + file + ")"); + } + private static void notifyEach(List listeners, IterationOrder iterationOrder, Consumer consumer, Supplier description) { iterationOrder.forEach(listeners, listener -> { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java index 9107865d8872..e9e823fed1a7 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/CompositeTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,6 +21,7 @@ import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.UnrecoverableExceptions; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -95,6 +96,13 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e () -> "reportingEntryPublished(" + testIdentifier + ", " + entry + ")"); } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + notifyEach(testExecutionListeners, IterationOrder.ORIGINAL, + listener -> listener.fileEntryPublished(testIdentifier, file), + () -> "fileEntryPublished(" + testIdentifier + ", " + file + ")"); + } + private static void notifyEach(List listeners, IterationOrder iterationOrder, Consumer consumer, Supplier description) { iterationOrder.forEach(listeners, listener -> { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java index 3ea9c295384a..6bd73c4b641e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultDiscoveryRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.EngineDiscoveryRequest; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; @@ -51,15 +52,19 @@ final class DefaultDiscoveryRequest implements LauncherDiscoveryRequest { // Listener for test discovery that may abort on errors. private final LauncherDiscoveryListener discoveryListener; + private final OutputDirectoryProvider outputDirectoryProvider; + DefaultDiscoveryRequest(List selectors, List engineFilters, List> discoveryFilters, List postDiscoveryFilters, - LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener) { + LauncherConfigurationParameters configurationParameters, LauncherDiscoveryListener discoveryListener, + OutputDirectoryProvider outputDirectoryProvider) { this.selectors = selectors; this.engineFilters = engineFilters; this.discoveryFilters = discoveryFilters; this.postDiscoveryFilters = postDiscoveryFilters; this.configurationParameters = configurationParameters; this.discoveryListener = discoveryListener; + this.outputDirectoryProvider = outputDirectoryProvider; } @Override @@ -91,7 +96,11 @@ public ConfigurationParameters getConfigurationParameters() { @Override public LauncherDiscoveryListener getDiscoveryListener() { - return discoveryListener; + return this.discoveryListener; } + @Override + public OutputDirectoryProvider getOutputDirectoryProvider() { + return this.outputDirectoryProvider; + } } 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 b6473d86a86a..5ebc4f9a638e 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java index 6a88425416b0..132618cdb5c0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java index 10965c631eb9..018eb41cf8a5 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DefaultLauncherSession.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java index 18a95fabdb3b..f2e50634a2ef 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingEngineExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -51,4 +52,8 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e delegate.reportingEntryPublished(testDescriptor, entry); } + @Override + public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + delegate.fileEntryPublished(testDescriptor, file); + } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java index fa0706b5dbef..3cc6be2316f0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/DelegatingLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java index 8a67aaae4888..018de8fb1b39 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryErrorDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 09cb6231b9a7..09c895a69684 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -75,8 +75,7 @@ public EngineDiscoveryOrchestrator(Iterable testEngines, * {@linkplain TestDescriptor#prune() prunes} the resulting test tree. */ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase) { - Map result = discover(request, phase, UniqueId::forEngine); - return new LauncherDiscoveryResult(result, request.getConfigurationParameters()); + return discover(request, phase, UniqueId::forEngine); } /** @@ -94,17 +93,18 @@ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase * will not emit start or emit events for engines without tests. */ public LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, UniqueId parentId) { - Map testEngines = discover(request, phase, parentId::appendEngine); - LauncherDiscoveryResult result = new LauncherDiscoveryResult(testEngines, request.getConfigurationParameters()); + LauncherDiscoveryResult result = discover(request, phase, parentId::appendEngine); return result.withRetainedEngines(TestDescriptor::containsTests); } - private Map discover(LauncherDiscoveryRequest request, Phase phase, + private LauncherDiscoveryResult discover(LauncherDiscoveryRequest request, Phase phase, Function uniqueIdCreator) { LauncherDiscoveryListener listener = getLauncherDiscoveryListener(request); listener.launcherDiscoveryStarted(request); try { - return discoverSafely(request, phase, listener, uniqueIdCreator); + Map testEngines = discoverSafely(request, phase, listener, uniqueIdCreator); + return new LauncherDiscoveryResult(testEngines, request.getConfigurationParameters(), + request.getOutputDirectoryProvider()); } finally { listener.launcherDiscoveryFinished(request); @@ -156,8 +156,14 @@ private TestDescriptor discoverEngineRoot(TestEngine testEngine, LauncherDiscove } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - String message = String.format("TestEngine with ID '%s' failed to discover tests", testEngine.getId()); - JUnitException cause = new JUnitException(message, throwable); + JUnitException cause = null; + if (throwable instanceof LinkageError) { + cause = ClasspathAlignmentChecker.check((LinkageError) throwable).orElse(null); + } + if (cause == null) { + String message = String.format("TestEngine with ID '%s' failed to discover tests", testEngine.getId()); + cause = new JUnitException(message, throwable); + } listener.engineDiscoveryFinished(uniqueEngineId, EngineDiscoveryResult.failed(cause)); return new EngineDiscoveryErrorDescriptor(uniqueEngineId, testEngine, cause); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java index 460e8d271825..61b57a24b67b 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -33,7 +33,7 @@ class EngineDiscoveryResultValidator { /** - * Perform common validation checks. + * Perform common validation checks. * * @throws org.junit.platform.commons.PreconditionViolationException if any check fails */ 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 c000c09ce6cc..aedf22950cc1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -28,6 +28,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -166,7 +167,8 @@ public void execute(LauncherDiscoveryResult discoveryResult, EngineExecutionList TestExecutionResult.failed(((EngineDiscoveryErrorDescriptor) engineDescriptor).getCause())); } else { - execute(engineDescriptor, listener, configurationParameters, testEngine); + execute(engineDescriptor, listener, configurationParameters, testEngine, + discoveryResult.getOutputDirectoryProvider()); } } } @@ -190,18 +192,27 @@ private ListenerRegistry buildListenerRegistryForExecutio } private void execute(TestDescriptor engineDescriptor, EngineExecutionListener listener, - ConfigurationParameters configurationParameters, TestEngine testEngine) { + ConfigurationParameters configurationParameters, TestEngine testEngine, + OutputDirectoryProvider outputDirectoryProvider) { OutcomeDelayingEngineExecutionListener delayingListener = new OutcomeDelayingEngineExecutionListener(listener, engineDescriptor); try { - testEngine.execute(new ExecutionRequest(engineDescriptor, delayingListener, configurationParameters)); + testEngine.execute(ExecutionRequest.create(engineDescriptor, delayingListener, configurationParameters, + outputDirectoryProvider)); delayingListener.reportEngineOutcome(); } catch (Throwable throwable) { UnrecoverableExceptions.rethrowIfUnrecoverable(throwable); - delayingListener.reportEngineFailure(new JUnitException( - String.format("TestEngine with ID '%s' failed to execute tests", testEngine.getId()), throwable)); + JUnitException cause = null; + if (throwable instanceof LinkageError) { + cause = ClasspathAlignmentChecker.check((LinkageError) throwable).orElse(null); + } + if (cause == null) { + String message = String.format("TestEngine with ID '%s' failed to execute tests", testEngine.getId()); + cause = new JUnitException(message, throwable); + } + delayingListener.reportEngineFailure(cause); } } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java index 830855532d87..10e939f1ce84 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineFilterer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java index f6305283b1b9..878df7d3c207 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/EngineIdValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java index 54e3b7c2ae38..9f68943b28cf 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ExecutionListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -61,6 +62,11 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e this.testExecutionListener.reportingEntryPublished(getTestIdentifier(testDescriptor), entry); } + @Override + public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + this.testExecutionListener.fileEntryPublished(getTestIdentifier(testDescriptor), file); + } + private TestIdentifier getTestIdentifier(TestDescriptor testDescriptor) { return this.testPlan.getTestIdentifier(testDescriptor.getUniqueId()); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java new file mode 100644 index 000000000000..b85d3176668a --- /dev/null +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import org.junit.platform.commons.util.Preconditions; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId.Segment; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; + +/** + * Hierarchical {@link OutputDirectoryProvider} that creates directories based on + * the unique ID segments of a {@link TestDescriptor}. + * + * @since 1.12 + */ +class HierarchicalOutputDirectoryProvider implements OutputDirectoryProvider { + + private static final Pattern FORBIDDEN_CHARS = Pattern.compile("[^a-z0-9.,_\\-() ]", Pattern.CASE_INSENSITIVE); + private static final String REPLACEMENT = "_"; + + private final Supplier rootDirSupplier; + private volatile Path rootDir; + + HierarchicalOutputDirectoryProvider(Supplier rootDirSupplier) { + this.rootDirSupplier = rootDirSupplier; + } + + @Override + public Path createOutputDirectory(TestDescriptor testDescriptor) throws IOException { + Preconditions.notNull(testDescriptor, "testDescriptor must not be null"); + + List segments = testDescriptor.getUniqueId().getSegments(); + Path relativePath = segments.stream() // + .skip(1) // + .map(HierarchicalOutputDirectoryProvider::toSanitizedPath) // + .reduce(toSanitizedPath(segments.get(0)), Path::resolve); + return Files.createDirectories(getRootDirectory().resolve(relativePath)); + } + + @Override + public synchronized Path getRootDirectory() { + if (rootDir == null) { + rootDir = rootDirSupplier.get(); + } + return rootDir; + } + + private static Path toSanitizedPath(Segment segment) { + return Paths.get(sanitizeName(segment.getValue())); + } + + private static String sanitizeName(String value) { + return FORBIDDEN_CHARS.matcher(value).replaceAll(REPLACEMENT); + } +} diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java index 56e9f427071a..44656ff04a51 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InterceptingLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java index 9195eaef2e97..315652f960a1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -31,12 +31,12 @@ class InternalTestPlan extends TestPlan { static InternalTestPlan from(LauncherDiscoveryResult discoveryResult) { TestPlan delegate = TestPlan.from(discoveryResult.getEngineTestDescriptors(), - discoveryResult.getConfigurationParameters()); + discoveryResult.getConfigurationParameters(), discoveryResult.getOutputDirectoryProvider()); return new InternalTestPlan(discoveryResult, delegate); } private InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) { - super(delegate.containsTests(), delegate.getConfigurationParameters()); + super(delegate.containsTests(), delegate.getConfigurationParameters(), delegate.getOutputDirectoryProvider()); this.discoveryResult = discoveryResult; this.delegate = delegate; } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/IterationOrder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/IterationOrder.java index 3d1dbfcea19e..d622aff75b5f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/IterationOrder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/IterationOrder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java index 8efe4de2323b..c51cb1e23271 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java index 1fa5f9a462bc..a33793a346e9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherConfigurationParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java index cb480d2533e3..d1fa66711079 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,9 @@ package org.junit.platform.launcher.core; import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; import java.util.ArrayList; import java.util.Arrays; @@ -27,11 +29,14 @@ import org.junit.platform.engine.DiscoveryFilter; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.Filter; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.EngineFilter; +import org.junit.platform.launcher.LauncherConstants; import org.junit.platform.launcher.LauncherDiscoveryListener; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.PostDiscoveryFilter; import org.junit.platform.launcher.core.LauncherConfigurationParameters.Builder; +import org.junit.platform.launcher.listeners.OutputDir; import org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners; /** @@ -73,8 +78,7 @@ * ) * .configurationParameter("key1", "value1") * .configurationParameters(configParameterMap) - * .build(); - * + * .build(); * * @since 1.0 * @see org.junit.platform.engine.discovery.DiscoverySelectors @@ -107,6 +111,7 @@ public final class LauncherDiscoveryRequestBuilder { private final List discoveryListeners = new ArrayList<>(); private boolean implicitConfigurationParametersEnabled = true; private ConfigurationParameters parentConfigurationParameters; + private OutputDirectoryProvider outputDirectoryProvider; /** * Create a new {@code LauncherDiscoveryRequestBuilder}. @@ -283,6 +288,27 @@ public LauncherDiscoveryRequestBuilder listeners(LauncherDiscoveryListener... li return this; } + /** + * Set the {@link OutputDirectoryProvider} to use for the request. + * + *

If not specified, a default provider will be used that can be + * configured via the {@value LauncherConstants#OUTPUT_DIR_PROPERTY_NAME} + * configuration parameter. + * + * @param outputDirectoryProvider the output directory provider to use; + * never {@code null} + * @return this builder for method chaining + * @since 1.12 + * @see OutputDirectoryProvider + * @see LauncherConstants#OUTPUT_DIR_PROPERTY_NAME + */ + @API(status = EXPERIMENTAL, since = "1.12") + public LauncherDiscoveryRequestBuilder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryProvider) { + this.outputDirectoryProvider = Preconditions.notNull(outputDirectoryProvider, + "outputDirectoryProvider must not be null"); + return this; + } + private void storeFilter(Filter filter) { if (filter instanceof EngineFilter) { this.engineFilters.add((EngineFilter) filter); @@ -307,8 +333,18 @@ else if (filter instanceof DiscoveryFilter) { public LauncherDiscoveryRequest build() { LauncherConfigurationParameters launcherConfigurationParameters = buildLauncherConfigurationParameters(); LauncherDiscoveryListener discoveryListener = getLauncherDiscoveryListener(launcherConfigurationParameters); + OutputDirectoryProvider outputDirectoryProvider = getOutputDirectoryProvider(launcherConfigurationParameters); return new DefaultDiscoveryRequest(this.selectors, this.engineFilters, this.discoveryFilters, - this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener); + this.postDiscoveryFilters, launcherConfigurationParameters, discoveryListener, outputDirectoryProvider); + } + + private OutputDirectoryProvider getOutputDirectoryProvider( + LauncherConfigurationParameters configurationParameters) { + if (this.outputDirectoryProvider != null) { + return this.outputDirectoryProvider; + } + return new HierarchicalOutputDirectoryProvider( + () -> OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)).toPath()); } private LauncherConfigurationParameters buildLauncherConfigurationParameters() { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java index d65b4c3fe4d1..64adb2f55670 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherDiscoveryResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,6 +23,7 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; /** * Represents the result of test discovery of the configured @@ -35,11 +36,13 @@ public class LauncherDiscoveryResult { private final Map testEngineDescriptors; private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; LauncherDiscoveryResult(Map testEngineDescriptors, - ConfigurationParameters configurationParameters) { + ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { this.testEngineDescriptors = unmodifiableMap(new LinkedHashMap<>(testEngineDescriptors)); this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; } public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { @@ -47,7 +50,11 @@ public TestDescriptor getEngineTestDescriptor(TestEngine testEngine) { } ConfigurationParameters getConfigurationParameters() { - return configurationParameters; + return this.configurationParameters; + } + + OutputDirectoryProvider getOutputDirectoryProvider() { + return this.outputDirectoryProvider; } public Collection getTestEngines() { @@ -60,15 +67,16 @@ Collection getEngineTestDescriptors() { public LauncherDiscoveryResult withRetainedEngines(Predicate predicate) { Map prunedTestEngineDescriptors = retainEngines(predicate); - if (prunedTestEngineDescriptors.size() < testEngineDescriptors.size()) { - return new LauncherDiscoveryResult(prunedTestEngineDescriptors, configurationParameters); + if (prunedTestEngineDescriptors.size() < this.testEngineDescriptors.size()) { + return new LauncherDiscoveryResult(prunedTestEngineDescriptors, this.configurationParameters, + this.outputDirectoryProvider); } return this; } private Map retainEngines(Predicate predicate) { // @formatter:off - return testEngineDescriptors.entrySet() + return this.testEngineDescriptors.entrySet() .stream() .filter(entry -> predicate.test(entry.getValue())) .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java index a84296db623b..c756f27351f0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.platform.launcher.core; -import static java.util.Collections.emptyList; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.launcher.LauncherConstants.DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME; import static org.junit.platform.launcher.LauncherConstants.ENABLE_LAUNCHER_INTERCEPTORS; @@ -19,8 +18,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; +import java.util.function.Predicate; import org.apiguardian.api.API; import org.junit.platform.commons.PreconditionViolationException; @@ -146,12 +144,12 @@ private static DefaultLauncher createDefaultLauncher(LauncherConfig config, private static List collectLauncherInterceptors( LauncherConfigurationParameters configurationParameters) { + List interceptors = new ArrayList<>(); if (configurationParameters.getBoolean(ENABLE_LAUNCHER_INTERCEPTORS).orElse(false)) { - List interceptors = new ArrayList<>(); ServiceLoaderRegistry.load(LauncherInterceptor.class).forEach(interceptors::add); - return interceptors; } - return emptyList(); + interceptors.add(ClasspathAlignmentCheckingLauncherInterceptor.INSTANCE); + return interceptors; } private static Set collectTestEngines(LauncherConfig config) { @@ -198,15 +196,12 @@ private static void registerTestExecutionListeners(LauncherConfig config, Launch config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners); } - private static Stream loadAndFilterTestExecutionListeners( + private static Iterable loadAndFilterTestExecutionListeners( ConfigurationParameters configurationParameters) { - Iterable listeners = ServiceLoaderRegistry.load(TestExecutionListener.class); - String deactivatedListenersPattern = configurationParameters.get( - DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME).orElse(null); - // @formatter:off - return StreamSupport.stream(listeners.spliterator(), false) - .filter(ClassNamePatternFilterUtils.excludeMatchingClasses(deactivatedListenersPattern)); - // @formatter:on + Predicate classNameFilter = configurationParameters.get(DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME) // + .map(ClassNamePatternFilterUtils::excludeMatchingClassNames) // + .orElse(__ -> true); + return ServiceLoaderRegistry.load(TestExecutionListener.class, classNameFilter); } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherListenerRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherListenerRegistry.java index 9e1850e41e50..da2db7e7a07a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherListenerRegistry.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/LauncherListenerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java index c5dd3c6fc884..4a535c6bd99e 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ListenerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java index 9e140c655bf3..1afb4460d5b3 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/OutcomeDelayingEngineExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java index 83866088dcb6..f06339619841 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,24 +11,55 @@ package org.junit.platform.launcher.core; import static java.util.stream.Collectors.toList; -import static java.util.stream.StreamSupport.stream; +import java.util.ArrayList; +import java.util.List; import java.util.ServiceLoader; +import java.util.function.Function; +import java.util.function.Predicate; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.ServiceLoaderUtils; /** * @since 1.8 */ class ServiceLoaderRegistry { - static Iterable load(Class serviceProviderClass) { - Iterable listeners = ServiceLoader.load(serviceProviderClass, ClassLoaderUtils.getDefaultClassLoader()); - getLogger().config(() -> "Loaded " + serviceProviderClass.getSimpleName() + " instances: " - + stream(listeners.spliterator(), false).map(Object::toString).collect(toList())); - return listeners; + static Iterable load(Class type) { + return load(type, __ -> true, instances -> logLoadedInstances(type, instances, null)); + } + + static Iterable load(@SuppressWarnings("SameParameterValue") Class type, + Predicate classNameFilter) { + List exclusions = new ArrayList<>(); + Predicate collectingClassNameFilter = className -> { + boolean included = classNameFilter.test(className); + if (!included) { + exclusions.add(className); + } + return included; + }; + return load(type, collectingClassNameFilter, instances -> logLoadedInstances(type, instances, exclusions)); + } + + private static String logLoadedInstances(Class type, List instances, List exclusions) { + String typeName = type.getSimpleName(); + if (exclusions == null) { + return String.format("Loaded %s instances: %s", typeName, instances); + } + return String.format("Loaded %s instances: %s (excluded classes: %s)", typeName, instances, exclusions); + } + + private static List load(Class type, Predicate classNameFilter, + Function, String> logMessageSupplier) { + ServiceLoader serviceLoader = ServiceLoader.load(type, ClassLoaderUtils.getDefaultClassLoader()); + Predicate> providerPredicate = clazz -> classNameFilter.test(clazz.getName()); + List instances = ServiceLoaderUtils.filter(serviceLoader, providerPredicate).collect(toList()); + getLogger().config(() -> logMessageSupplier.apply(instances)); + return instances; } private static Logger getLogger() { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java index ada5a07778c3..73d57ed1d5d9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/ServiceLoaderTestEngineRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java index c0edfa8e80b0..dffde014867a 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/SessionPerRequestLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StackTracePruningEngineExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StackTracePruningEngineExecutionListener.java index 3063fd9b8073..6a6193337d89 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StackTracePruningEngineExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StackTracePruningEngineExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java index 32a135896554..65a5f8ba453c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java index b34fd72c1a15..85b69f64bb26 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/StreamInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.Optional; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.function.Consumer; /** @@ -22,6 +23,8 @@ */ class StreamInterceptor extends PrintStream { + private final Deque mostRecentOutputs = new ConcurrentLinkedDeque<>(); + private final PrintStream originalStream; private final Consumer unregisterAction; private final int maxNumberOfBytesPerThread; @@ -56,11 +59,18 @@ private StreamInterceptor(PrintStream originalStream, Consumer unre } void capture() { - output.get().mark(); + RewindableByteArrayOutputStream out = output.get(); + out.mark(); + pushToTop(out); } String consume() { - return output.get().rewind(); + RewindableByteArrayOutputStream out = output.get(); + String result = out.rewind(); + if (!out.isMarked()) { + mostRecentOutputs.remove(out); + } + return result; } void unregister() { @@ -69,8 +79,9 @@ void unregister() { @Override public void write(int b) { - RewindableByteArrayOutputStream out = output.get(); - if (out.isMarked() && out.size() < maxNumberOfBytesPerThread) { + RewindableByteArrayOutputStream out = getOutput(); + if (out != null && out.size() < maxNumberOfBytesPerThread) { + pushToTop(out); out.write(b); } super.write(b); @@ -83,16 +94,29 @@ public void write(byte[] b) { @Override public void write(byte[] buf, int off, int len) { - RewindableByteArrayOutputStream out = output.get(); - if (out.isMarked()) { + RewindableByteArrayOutputStream out = getOutput(); + if (out != null) { int actualLength = Math.max(0, Math.min(len, maxNumberOfBytesPerThread - out.size())); if (actualLength > 0) { + pushToTop(out); out.write(buf, off, actualLength); } } super.write(buf, off, len); } + private void pushToTop(RewindableByteArrayOutputStream out) { + if (!out.equals(mostRecentOutputs.peek())) { + mostRecentOutputs.remove(out); + mostRecentOutputs.push(out); + } + } + + private RewindableByteArrayOutputStream getOutput() { + RewindableByteArrayOutputStream out = output.get(); + return out.isMarked() ? out : mostRecentOutputs.peek(); + } + static class RewindableByteArrayOutputStream extends ByteArrayOutputStream { private final Deque markedPositions = new ArrayDeque<>(); diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java index ec4fe8418ca7..558e6824fbe0 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/TestEngineFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java index 08ef0e55b7c2..9417a1482856 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LegacyReportingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -46,9 +46,9 @@ private LegacyReportingUtils() { * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. * * @param testPlan the test plan that contains the {@code TestIdentifier}; - * never {@code null} + * never {@code null} * @param testIdentifier the identifier to determine the class name for; - * never {@code null} + * never {@code null} * @see TestIdentifier#getLegacyReportingName */ public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java index 6dd62495353a..3b6e37907565 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/LoggingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java index da39a9a2b6d4..0ef6826b016b 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/MutableTestExecutionSummary.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java index 31de20e55175..acff49f57a9d 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/OutputDir.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,15 +11,20 @@ package org.junit.platform.launcher.listeners; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; import java.security.SecureRandom; import java.util.Optional; +import java.util.function.BiPredicate; import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.util.StringUtils; @@ -27,9 +32,16 @@ @API(status = INTERNAL, since = "1.9") public class OutputDir { + private static final Pattern OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN = Pattern.compile( + Pattern.quote(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)); + public static OutputDir create(Optional customDir) { + return create(customDir, () -> Paths.get(".")); + } + + static OutputDir create(Optional customDir, Supplier currentWorkingDir) { try { - return createSafely(customDir, () -> Paths.get(".").toAbsolutePath()); + return createSafely(customDir, currentWorkingDir); } catch (IOException e) { throw new UncheckedIOException("Failed to create output dir", e); @@ -40,11 +52,21 @@ public static OutputDir create(Optional customDir) { * Package private for testing purposes. */ static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir) throws IOException { - Path cwd = currentWorkingDir.get(); + return createSafely(customDir, currentWorkingDir, new SecureRandom()); + } + + private static OutputDir createSafely(Optional customDir, Supplier currentWorkingDir, + SecureRandom random) throws IOException { + Path cwd = currentWorkingDir.get().toAbsolutePath(); Path outputDir; if (customDir.isPresent() && StringUtils.isNotBlank(customDir.get())) { - outputDir = cwd.resolve(customDir.get()); + String customPath = customDir.get(); + while (customPath.contains(OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)) { + customPath = OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER_PATTERN.matcher(customPath) // + .replaceFirst(String.valueOf(Math.abs(random.nextLong()))); + } + outputDir = cwd.resolve(customPath); } else if (Files.exists(cwd.resolve("pom.xml"))) { outputDir = cwd.resolve("target"); @@ -60,13 +82,15 @@ else if (containsFilesWithExtensions(cwd, ".gradle", ".gradle.kts")) { Files.createDirectories(outputDir); } - return new OutputDir(outputDir); + return new OutputDir(outputDir.normalize(), random); } private final Path path; + private final SecureRandom random; - private OutputDir(Path path) { + private OutputDir(Path path, SecureRandom random) { this.path = path; + this.random = random; } public Path toPath() { @@ -74,7 +98,7 @@ public Path toPath() { } public Path createFile(String prefix, String extension) throws UncheckedIOException { - String filename = String.format("%s-%d.%s", prefix, Math.abs(new SecureRandom().nextLong()), extension); + String filename = String.format("%s-%d.%s", prefix, Math.abs(random.nextLong()), extension); Path outputFile = path.resolve(filename); try { @@ -93,18 +117,18 @@ public Path createFile(String prefix, String extension) throws UncheckedIOExcept * supplied extensions. */ private static boolean containsFilesWithExtensions(Path dir, String... extensions) throws IOException { - return Files.find(dir, 1, // - (path, basicFileAttributes) -> { - if (basicFileAttributes.isRegularFile()) { - for (String extension : extensions) { - if (path.getFileName().toString().endsWith(extension)) { - return true; - } + BiPredicate matcher = (path, basicFileAttributes) -> { + if (basicFileAttributes.isRegularFile()) { + for (String extension : extensions) { + if (path.getFileName().toString().endsWith(extension)) { + return true; } } - return false; - }) // - .findFirst() // - .isPresent(); + } + return false; + }; + try (Stream pathStream = Files.find(dir, 1, matcher)) { + return pathStream.findFirst().isPresent(); + } } } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java index c01c09af2bf9..751888b2ae86 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/SummaryGeneratingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java index 800338127886..48328a32e77c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/TestExecutionSummary.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java index a7a02b662731..8b8b8d51f274 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,8 +17,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import org.apiguardian.api.API; import org.junit.platform.commons.logging.Logger; @@ -125,6 +127,8 @@ public class UniqueIdTrackingListener implements TestExecutionListener { */ public static final String DEFAULT_OUTPUT_FILE_PREFIX = "junit-platform-unique-ids"; + static final String WORKING_DIR_PROPERTY_NAME = "junit.platform.listeners.uid.tracking.working.dir"; + private final Logger logger = LoggerFactory.getLogger(UniqueIdTrackingListener.class); private final List uniqueIds = new ArrayList<>(); @@ -201,7 +205,9 @@ public void testPlanExecutionFinished(TestPlan testPlan) { private Path createOutputFile(ConfigurationParameters configurationParameters) { String prefix = configurationParameters.get(OUTPUT_FILE_PREFIX_PROPERTY_NAME) // .orElse(DEFAULT_OUTPUT_FILE_PREFIX); - return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME)) // + Supplier workingDirSupplier = () -> configurationParameters.get(WORKING_DIR_PROPERTY_NAME).map( + Paths::get).orElseGet(() -> Paths.get(".")); + return OutputDir.create(configurationParameters.get(OUTPUT_DIR_PROPERTY_NAME), workingDirSupplier) // .createFile(prefix, "txt"); } diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java index e0dd78eda8fc..11a7429219c9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java index f94d256cbe16..0f4153ce5b00 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 70a183621a41..757d17f6f400 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java index 40a53a7d34cb..e5c31298abdd 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java index b3d452bd8b42..c1c13327652c 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java index ccd7cc0deaab..938420d58c1f 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/listeners/session/LauncherSessionListeners.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java index be9fda237e56..4d3899199cee 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/DequeStack.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java index a8b1be00cf76..7adc69050a06 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java index 475fda07d49f..541ceb976bb1 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Operators.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java index 50086134e9f8..0882acda8ede 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java index c2220484337a..5ca542b8c0f9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java index a36c11a06cfb..695ef20c70ac 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ParseStatus.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java index 2a10848dc14b..417cf3e52aad 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Parser.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java index 6c92b7846de7..b54d40f5b6a9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/ShuntingYard.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java index 50e9c930ace7..a1a4cd5d0fa9 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Stack.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java index 51bf079075ca..ac101b2a6810 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpression.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java index 4301d9277f4b..350e6f4d7a68 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TagExpressions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java index f8451100ae0d..4b90dd72c915 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Token.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java index 511745d8fac6..161cb229f6e7 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/TokenWith.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java index a1b3661b0e42..f21dd73aa332 100644 --- a/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java +++ b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/tagexpression/Tokenizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java b/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java index 9d79f3f4166a..e4e492183f5a 100644 --- a/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java +++ b/junit-platform-launcher/src/module/org.junit.platform.launcher/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/nativeImage/initialize-at-build-time b/junit-platform-launcher/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..4b3770ab5fb1 --- /dev/null +++ b/junit-platform-launcher/src/nativeImage/initialize-at-build-time @@ -0,0 +1,19 @@ +org.junit.platform.launcher.LauncherSessionListener$1 +org.junit.platform.launcher.TestIdentifier +org.junit.platform.launcher.core.DefaultLauncher +org.junit.platform.launcher.core.DefaultLauncherConfig +org.junit.platform.launcher.core.EngineDiscoveryOrchestrator +org.junit.platform.launcher.core.EngineExecutionOrchestrator +org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider +org.junit.platform.launcher.core.InternalTestPlan +org.junit.platform.launcher.core.LauncherConfig +org.junit.platform.launcher.core.LauncherConfigurationParameters +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$1 +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2 +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3 +org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$4 +org.junit.platform.launcher.core.LauncherDiscoveryResult +org.junit.platform.launcher.core.LauncherListenerRegistry +org.junit.platform.launcher.core.ListenerRegistry +org.junit.platform.launcher.core.SessionPerRequestLauncher +org.junit.platform.launcher.listeners.UniqueIdTrackingListener diff --git a/junit-platform-launcher/src/test/README.md b/junit-platform-launcher/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-launcher/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java index 102634bdfbf7..45d9af72b788 100644 --- a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/ConfigurationParametersFactoryForTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java index 7036c0609b60..c3909dd105df 100644 --- a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/LauncherFactoryForTestingPurposesOnly.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java new file mode 100644 index 000000000000..b4d30b2b7b8f --- /dev/null +++ b/junit-platform-launcher/src/testFixtures/java/org/junit/platform/launcher/core/OutputDirectoryProviders.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import java.nio.file.Path; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; + +public class OutputDirectoryProviders { + + public static OutputDirectoryProvider dummyOutputDirectoryProvider() { + return new HierarchicalOutputDirectoryProvider(() -> { + throw new JUnitException("This should not be called; use a real provider instead"); + }); + } + + public static OutputDirectoryProvider hierarchicalOutputDirectoryProvider(Path rootDir) { + return new HierarchicalOutputDirectoryProvider(() -> rootDir); + } +} diff --git a/junit-platform-reporting/junit-platform-reporting.gradle.kts b/junit-platform-reporting/junit-platform-reporting.gradle.kts index b95816fcfc2d..e9094c04f2e1 100644 --- a/junit-platform-reporting/junit-platform-reporting.gradle.kts +++ b/junit-platform-reporting/junit-platform-reporting.gradle.kts @@ -1,6 +1,8 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.native-image-properties") id("junitbuild.shadow-conventions") + `java-test-fixtures` } description = "JUnit Platform Reporting" @@ -10,16 +12,23 @@ dependencies { api(projects.junitPlatformLauncher) compileOnlyApi(libs.apiguardian) + compileOnlyApi(libs.openTestReporting.tooling.spi) shadowed(libs.openTestReporting.events) osgiVerification(projects.junitJupiterEngine) osgiVerification(projects.junitPlatformLauncher) + osgiVerification(libs.openTestReporting.tooling.spi) + + testFixturesApi(projects.junitJupiterApi) } tasks { shadowJar { - relocate("org.opentest4j.reporting", "org.junit.platform.reporting.shadow.org.opentest4j.reporting") + listOf("events", "schema").forEach { name -> + val packageName = "org.opentest4j.reporting.${name}" + relocate(packageName, "org.junit.platform.reporting.shadow.${packageName}") + } from(projectDir) { include("LICENSE-open-test-reporting.md") into("META-INF") diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java index 68ac58467101..318a6b5351ed 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/LegacyReportingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,8 +21,8 @@ * Utility methods for dealing with legacy reporting infrastructure, such as * reporting systems built on the Ant-based XML reporting format for JUnit 4. * - * This class was formerly from {@code junit-platform-launcher} - * in {@link org.junit.platform.launcher.listeners} package. + *

This class was formerly from {@code junit-platform-launcher} in the + * {@link org.junit.platform.launcher.listeners} package. * * @since 1.0.3 */ @@ -44,13 +44,14 @@ private LegacyReportingUtils() { * {@linkplain TestIdentifier#getLegacyReportingName legacy reporting name}. * * @param testPlan the test plan that contains the {@code TestIdentifier}; - * never {@code null} + * never {@code null} * @param testIdentifier the identifier to determine the class name for; - * never {@code null} + * never {@code null} * @see TestIdentifier#getLegacyReportingName */ @SuppressWarnings("deprecation") public static String getClassName(TestPlan testPlan, TestIdentifier testIdentifier) { return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, testIdentifier); } + } diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java index 29ef317d3767..06bd0ecfc7a0 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java index b99315243944..f1ce85be73cb 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportData.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java index e81d3abbd10c..d3ae6b008bd9 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/legacy/xml/XmlReportWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static java.text.MessageFormat.format; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static java.util.Collections.emptyList; +import static java.util.Collections.unmodifiableMap; import static java.util.Comparator.naturalOrder; import static java.util.function.Function.identity; import static java.util.stream.Collectors.counting; @@ -30,6 +31,7 @@ import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SKIPPED; import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.AggregatedTestResult.Type.SUCCESS; +import java.io.IOException; import java.io.Writer; import java.net.InetAddress; import java.net.UnknownHostException; @@ -38,6 +40,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; @@ -68,6 +71,17 @@ */ class XmlReportWriter { + static final char ILLEGAL_CHARACTER_REPLACEMENT = '\uFFFD'; + + private static final Map REPLACEMENTS_IN_ATTRIBUTE_VALUES; + static { + Map tmp = new HashMap<>(3); + tmp.put('\n', " "); + tmp.put('\r', " "); + tmp.put('\t', " "); + REPLACEMENTS_IN_ATTRIBUTE_VALUES = unmodifiableMap(tmp); + } + // Using zero-width assertions in the split pattern simplifies the splitting process: All split parts // (including the first and last one) can be used directly, without having to re-add separator characters. private static final Pattern CDATA_SPLIT_PATTERN = Pattern.compile("(?<=]])(?=>)"); @@ -101,243 +115,271 @@ private boolean shouldInclude(TestPlan testPlan, TestIdentifier testIdentifier) private void writeXmlReport(TestIdentifier testIdentifier, Map tests, Writer out) throws XMLStreamException { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - XMLStreamWriter xmlWriter = factory.createXMLStreamWriter(out); - xmlWriter.writeStartDocument("UTF-8", "1.0"); - newLine(xmlWriter); - writeTestsuite(testIdentifier, tests, xmlWriter); - xmlWriter.writeEndDocument(); - xmlWriter.flush(); - xmlWriter.close(); + try (XmlReport report = new XmlReport(out)) { + report.write(testIdentifier, tests); + } } - private void writeTestsuite(TestIdentifier testIdentifier, Map tests, - XMLStreamWriter writer) throws XMLStreamException { + private class XmlReport implements AutoCloseable { - // NumberFormat is not thread-safe. Thus, we instantiate it here and pass it to - // writeTestcase instead of using a constant - NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + private final XMLStreamWriter xml; + private final ReplacingWriter out; - writer.writeStartElement("testsuite"); + XmlReport(Writer out) throws XMLStreamException { + this.out = new ReplacingWriter(out); + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + this.xml = factory.createXMLStreamWriter(this.out); + } - writeSuiteAttributes(testIdentifier, tests.values(), numberFormat, writer); + void write(TestIdentifier testIdentifier, Map tests) + throws XMLStreamException { + xml.writeStartDocument("UTF-8", "1.0"); + newLine(); + writeTestsuite(testIdentifier, tests); + xml.writeEndDocument(); + } - newLine(writer); - writeSystemProperties(writer); + private void writeTestsuite(TestIdentifier testIdentifier, Map tests) + throws XMLStreamException { - for (Entry entry : tests.entrySet()) { - writeTestcase(entry.getKey(), entry.getValue(), numberFormat, writer); - } + // NumberFormat is not thread-safe. Thus, we instantiate it here and pass it to + // writeTestcase instead of using a constant + NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); - writeOutputElement("system-out", formatNonStandardAttributesAsString(testIdentifier), writer); + xml.writeStartElement("testsuite"); - writer.writeEndElement(); - newLine(writer); - } + writeSuiteAttributes(testIdentifier, tests.values(), numberFormat); - private void writeSuiteAttributes(TestIdentifier testIdentifier, Collection testResults, - NumberFormat numberFormat, XMLStreamWriter writer) throws XMLStreamException { + newLine(); + writeSystemProperties(); - writeAttributeSafely(writer, "name", testIdentifier.getDisplayName()); - writeTestCounts(testResults, writer); - writeAttributeSafely(writer, "time", getTime(testIdentifier, numberFormat)); - writeAttributeSafely(writer, "hostname", getHostname().orElse("")); - writeAttributeSafely(writer, "timestamp", ISO_LOCAL_DATE_TIME.format(getCurrentDateTime())); - } + for (Entry entry : tests.entrySet()) { + writeTestcase(entry.getKey(), entry.getValue(), numberFormat); + } - private void writeTestCounts(Collection testResults, XMLStreamWriter writer) - throws XMLStreamException { - Map counts = testResults.stream().map(it -> it.type).collect(groupingBy(identity(), counting())); - long total = counts.values().stream().mapToLong(Long::longValue).sum(); - writeAttributeSafely(writer, "tests", String.valueOf(total)); - writeAttributeSafely(writer, "skipped", counts.getOrDefault(SKIPPED, 0L).toString()); - writeAttributeSafely(writer, "failures", counts.getOrDefault(FAILURE, 0L).toString()); - writeAttributeSafely(writer, "errors", counts.getOrDefault(ERROR, 0L).toString()); - } + writeOutputElement("system-out", formatNonStandardAttributesAsString(testIdentifier)); - private void writeSystemProperties(XMLStreamWriter writer) throws XMLStreamException { - writer.writeStartElement("properties"); - newLine(writer); - Properties systemProperties = System.getProperties(); - for (String propertyName : new TreeSet<>(systemProperties.stringPropertyNames())) { - writer.writeEmptyElement("property"); - writeAttributeSafely(writer, "name", propertyName); - writeAttributeSafely(writer, "value", systemProperties.getProperty(propertyName)); - newLine(writer); - } - writer.writeEndElement(); - newLine(writer); - } + xml.writeEndElement(); + newLine(); + } - private void writeTestcase(TestIdentifier testIdentifier, AggregatedTestResult testResult, - NumberFormat numberFormat, XMLStreamWriter writer) throws XMLStreamException { + private void writeSuiteAttributes(TestIdentifier testIdentifier, Collection testResults, + NumberFormat numberFormat) throws XMLStreamException { - writer.writeStartElement("testcase"); + writeAttributeSafely("name", testIdentifier.getDisplayName()); + writeTestCounts(testResults); + writeAttributeSafely("time", getTime(testIdentifier, numberFormat)); + writeAttributeSafely("hostname", getHostname().orElse("")); + writeAttributeSafely("timestamp", ISO_LOCAL_DATE_TIME.format(getCurrentDateTime())); + } - writeAttributeSafely(writer, "name", getName(testIdentifier)); - writeAttributeSafely(writer, "classname", getClassName(testIdentifier)); - writeAttributeSafely(writer, "time", getTime(testIdentifier, numberFormat)); - newLine(writer); + private void writeTestCounts(Collection testResults) throws XMLStreamException { + Map counts = testResults.stream().map(it -> it.type).collect( + groupingBy(identity(), counting())); + long total = counts.values().stream().mapToLong(Long::longValue).sum(); + writeAttributeSafely("tests", String.valueOf(total)); + writeAttributeSafely("skipped", counts.getOrDefault(SKIPPED, 0L).toString()); + writeAttributeSafely("failures", counts.getOrDefault(FAILURE, 0L).toString()); + writeAttributeSafely("errors", counts.getOrDefault(ERROR, 0L).toString()); + } - writeSkippedOrErrorOrFailureElement(testIdentifier, testResult, writer); + private void writeSystemProperties() throws XMLStreamException { + xml.writeStartElement("properties"); + newLine(); + Properties systemProperties = System.getProperties(); + for (String propertyName : new TreeSet<>(systemProperties.stringPropertyNames())) { + xml.writeEmptyElement("property"); + writeAttributeSafely("name", propertyName); + writeAttributeSafely("value", systemProperties.getProperty(propertyName)); + newLine(); + } + xml.writeEndElement(); + newLine(); + } - List systemOutElements = new ArrayList<>(); - List systemErrElements = new ArrayList<>(); - systemOutElements.add(formatNonStandardAttributesAsString(testIdentifier)); - collectReportEntries(testIdentifier, systemOutElements, systemErrElements); - writeOutputElements("system-out", systemOutElements, writer); - writeOutputElements("system-err", systemErrElements, writer); + private void writeTestcase(TestIdentifier testIdentifier, AggregatedTestResult testResult, + NumberFormat numberFormat) throws XMLStreamException { - writer.writeEndElement(); - newLine(writer); - } + xml.writeStartElement("testcase"); - private String getName(TestIdentifier testIdentifier) { - return testIdentifier.getLegacyReportingName(); - } + writeAttributeSafely("name", getName(testIdentifier)); + writeAttributeSafely("classname", getClassName(testIdentifier)); + writeAttributeSafely("time", getTime(testIdentifier, numberFormat)); + newLine(); - private String getClassName(TestIdentifier testIdentifier) { - return LegacyReportingUtils.getClassName(this.reportData.getTestPlan(), testIdentifier); - } + writeSkippedOrErrorOrFailureElement(testIdentifier, testResult); + + List systemOutElements = new ArrayList<>(); + List systemErrElements = new ArrayList<>(); + systemOutElements.add(formatNonStandardAttributesAsString(testIdentifier)); + collectReportEntries(testIdentifier, systemOutElements, systemErrElements); + writeOutputElements("system-out", systemOutElements); + writeOutputElements("system-err", systemErrElements); + + xml.writeEndElement(); + newLine(); + } - private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, AggregatedTestResult testResult, - XMLStreamWriter writer) throws XMLStreamException { + private String getName(TestIdentifier testIdentifier) { + return testIdentifier.getLegacyReportingName(); + } - if (testResult.type == SKIPPED) { - writeSkippedElement(this.reportData.getSkipReason(testIdentifier), writer); + private String getClassName(TestIdentifier testIdentifier) { + return LegacyReportingUtils.getClassName(reportData.getTestPlan(), testIdentifier); } - else { - Map>> throwablesByType = testResult.getThrowablesByType(); - for (Type type : EnumSet.of(FAILURE, ERROR)) { - for (Optional throwable : throwablesByType.getOrDefault(type, emptyList())) { - writeErrorOrFailureElement(type, throwable.orElse(null), writer); + + private void writeSkippedOrErrorOrFailureElement(TestIdentifier testIdentifier, AggregatedTestResult testResult) + throws XMLStreamException { + + if (testResult.type == SKIPPED) { + writeSkippedElement(reportData.getSkipReason(testIdentifier), xml); + } + else { + Map>> throwablesByType = testResult.getThrowablesByType(); + for (Type type : EnumSet.of(FAILURE, ERROR)) { + for (Optional throwable : throwablesByType.getOrDefault(type, emptyList())) { + writeErrorOrFailureElement(type, throwable.orElse(null), xml); + } } } } - } - private void writeSkippedElement(String reason, XMLStreamWriter writer) throws XMLStreamException { - if (isNotBlank(reason)) { - writer.writeStartElement("skipped"); - writeCDataSafely(writer, reason); - writer.writeEndElement(); - } - else { - writer.writeEmptyElement("skipped"); + private void writeSkippedElement(String reason, XMLStreamWriter writer) throws XMLStreamException { + if (isNotBlank(reason)) { + writer.writeStartElement("skipped"); + writeCDataSafely(reason); + writer.writeEndElement(); + } + else { + writer.writeEmptyElement("skipped"); + } + newLine(); } - newLine(writer); - } - private void writeErrorOrFailureElement(Type type, Throwable throwable, XMLStreamWriter writer) - throws XMLStreamException { + private void writeErrorOrFailureElement(Type type, Throwable throwable, XMLStreamWriter writer) + throws XMLStreamException { - String elementName = type == FAILURE ? "failure" : "error"; - if (throwable != null) { - writer.writeStartElement(elementName); - writeFailureAttributesAndContent(throwable, writer); - writer.writeEndElement(); - } - else { - writer.writeEmptyElement(elementName); + String elementName = type == FAILURE ? "failure" : "error"; + if (throwable != null) { + writer.writeStartElement(elementName); + writeFailureAttributesAndContent(throwable); + writer.writeEndElement(); + } + else { + writer.writeEmptyElement(elementName); + } + newLine(); } - newLine(writer); - } - private void writeFailureAttributesAndContent(Throwable throwable, XMLStreamWriter writer) - throws XMLStreamException { + private void writeFailureAttributesAndContent(Throwable throwable) throws XMLStreamException { - if (throwable.getMessage() != null) { - writeAttributeSafely(writer, "message", throwable.getMessage()); + if (throwable.getMessage() != null) { + writeAttributeSafely("message", throwable.getMessage()); + } + writeAttributeSafely("type", throwable.getClass().getName()); + writeCDataSafely(readStackTrace(throwable)); } - writeAttributeSafely(writer, "type", throwable.getClass().getName()); - writeCDataSafely(writer, readStackTrace(throwable)); - } - private void collectReportEntries(TestIdentifier testIdentifier, List systemOutElements, - List systemErrElements) { - List entries = this.reportData.getReportEntries(testIdentifier); - if (!entries.isEmpty()) { - List systemOutElementsForCapturedOutput = new ArrayList<>(); - StringBuilder formattedReportEntries = new StringBuilder(); - for (int i = 0; i < entries.size(); i++) { - ReportEntry reportEntry = entries.get(i); - Map keyValuePairs = new LinkedHashMap<>(reportEntry.getKeyValuePairs()); - removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDOUT_REPORT_ENTRY_KEY, - systemOutElementsForCapturedOutput); - removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDERR_REPORT_ENTRY_KEY, systemErrElements); - if (!keyValuePairs.isEmpty()) { - buildReportEntryDescription(reportEntry.getTimestamp(), keyValuePairs, i + 1, - formattedReportEntries); + private void collectReportEntries(TestIdentifier testIdentifier, List systemOutElements, + List systemErrElements) { + List entries = reportData.getReportEntries(testIdentifier); + if (!entries.isEmpty()) { + List systemOutElementsForCapturedOutput = new ArrayList<>(); + StringBuilder formattedReportEntries = new StringBuilder(); + for (int i = 0; i < entries.size(); i++) { + ReportEntry reportEntry = entries.get(i); + Map keyValuePairs = new LinkedHashMap<>(reportEntry.getKeyValuePairs()); + removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDOUT_REPORT_ENTRY_KEY, + systemOutElementsForCapturedOutput); + removeIfPresentAndAddAsSeparateElement(keyValuePairs, STDERR_REPORT_ENTRY_KEY, systemErrElements); + if (!keyValuePairs.isEmpty()) { + buildReportEntryDescription(reportEntry.getTimestamp(), keyValuePairs, i + 1, + formattedReportEntries); + } } + systemOutElements.add(formattedReportEntries.toString().trim()); + systemOutElements.addAll(systemOutElementsForCapturedOutput); } - systemOutElements.add(formattedReportEntries.toString().trim()); - systemOutElements.addAll(systemOutElementsForCapturedOutput); } - } - private void removeIfPresentAndAddAsSeparateElement(Map keyValuePairs, String key, - List elements) { - String value = keyValuePairs.remove(key); - if (value != null) { - elements.add(value); + private void removeIfPresentAndAddAsSeparateElement(Map keyValuePairs, String key, + List elements) { + String value = keyValuePairs.remove(key); + if (value != null) { + elements.add(value); + } } - } - private void buildReportEntryDescription(LocalDateTime timestamp, Map keyValuePairs, - int entryNumber, StringBuilder result) { - result.append( - format("Report Entry #{0} (timestamp: {1})\n", entryNumber, ISO_LOCAL_DATE_TIME.format(timestamp))); - keyValuePairs.forEach((key, value) -> result.append(format("\t- {0}: {1}\n", key, value))); - } + private void buildReportEntryDescription(LocalDateTime timestamp, Map keyValuePairs, + int entryNumber, StringBuilder result) { + result.append( + format("Report Entry #{0} (timestamp: {1})\n", entryNumber, ISO_LOCAL_DATE_TIME.format(timestamp))); + keyValuePairs.forEach((key, value) -> result.append(format("\t- {0}: {1}\n", key, value))); + } - private String getTime(TestIdentifier testIdentifier, NumberFormat numberFormat) { - return numberFormat.format(this.reportData.getDurationInSeconds(testIdentifier)); - } + private String getTime(TestIdentifier testIdentifier, NumberFormat numberFormat) { + return numberFormat.format(reportData.getDurationInSeconds(testIdentifier)); + } - private Optional getHostname() { - try { - return Optional.ofNullable(InetAddress.getLocalHost().getHostName()); + private Optional getHostname() { + try { + return Optional.ofNullable(InetAddress.getLocalHost().getHostName()); + } + catch (UnknownHostException e) { + return Optional.empty(); + } } - catch (UnknownHostException e) { - return Optional.empty(); + + private LocalDateTime getCurrentDateTime() { + return LocalDateTime.now(reportData.getClock()).withNano(0); } - } - private LocalDateTime getCurrentDateTime() { - return LocalDateTime.now(this.reportData.getClock()).withNano(0); - } + private String formatNonStandardAttributesAsString(TestIdentifier testIdentifier) { + return "unique-id: " + testIdentifier.getUniqueId() // + + "\ndisplay-name: " + testIdentifier.getDisplayName(); + } - private String formatNonStandardAttributesAsString(TestIdentifier testIdentifier) { - return "unique-id: " + testIdentifier.getUniqueId() // - + "\ndisplay-name: " + testIdentifier.getDisplayName(); - } + private void writeOutputElements(String elementName, List elements) throws XMLStreamException { + for (String content : elements) { + writeOutputElement(elementName, content); + } + } - private void writeOutputElements(String elementName, List elements, XMLStreamWriter writer) - throws XMLStreamException { - for (String content : elements) { - writeOutputElement(elementName, content, writer); + private void writeOutputElement(String elementName, String content) throws XMLStreamException { + xml.writeStartElement(elementName); + writeCDataSafely("\n" + content + "\n"); + xml.writeEndElement(); + newLine(); } - } - private void writeOutputElement(String elementName, String content, XMLStreamWriter writer) - throws XMLStreamException { - writer.writeStartElement(elementName); - writeCDataSafely(writer, "\n" + content + "\n"); - writer.writeEndElement(); - newLine(writer); - } + private void writeAttributeSafely(String name, String value) throws XMLStreamException { + // Workaround for XMLStreamWriter implementations that don't escape + // '\n', '\r', and '\t' characters in attribute values + xml.flush(); + out.setWhitespaceReplacingEnabled(true); + xml.writeAttribute(name, replaceIllegalCharacters(value)); + xml.flush(); + out.setWhitespaceReplacingEnabled(false); + } - private void writeAttributeSafely(XMLStreamWriter writer, String name, String value) throws XMLStreamException { - writer.writeAttribute(name, escapeIllegalChars(value)); - } + private void writeCDataSafely(String data) throws XMLStreamException { + for (String safeDataPart : CDATA_SPLIT_PATTERN.split(replaceIllegalCharacters(data))) { + xml.writeCData(safeDataPart); + } + } - private void writeCDataSafely(XMLStreamWriter writer, String data) throws XMLStreamException { - for (String safeDataPart : CDATA_SPLIT_PATTERN.split(escapeIllegalChars(data))) { - writer.writeCData(safeDataPart); + private void newLine() throws XMLStreamException { + xml.writeCharacters("\n"); + } + + @Override + public void close() throws XMLStreamException { + xml.flush(); + xml.close(); } } - static String escapeIllegalChars(String text) { + static String replaceIllegalCharacters(String text) { if (text.codePoints().allMatch(XmlReportWriter::isAllowedXmlCharacter)) { return text; } @@ -346,14 +388,14 @@ static String escapeIllegalChars(String text) { if (isAllowedXmlCharacter(codePoint)) { result.appendCodePoint(codePoint); } - else { // use a Character Reference (cf. https://www.w3.org/TR/xml/#NT-CharRef) - result.append("&#").append(codePoint).append(';'); + else { + result.append(ILLEGAL_CHARACTER_REPLACEMENT); } }); return result.toString(); } - private static boolean isAllowedXmlCharacter(int codePoint) { + static boolean isAllowedXmlCharacter(int codePoint) { // source: https://www.w3.org/TR/xml/#charsets return codePoint == 0x9 // || codePoint == 0xA // @@ -363,15 +405,6 @@ private static boolean isAllowedXmlCharacter(int codePoint) { || (codePoint >= 0x10000 && codePoint <= 0x10FFFF); } - private void newLine(XMLStreamWriter xmlWriter) throws XMLStreamException { - xmlWriter.writeCharacters("\n"); - } - - private static boolean isFailure(TestExecutionResult result) { - Optional throwable = result.getThrowable(); - return throwable.isPresent() && throwable.get() instanceof AssertionError; - } - static class AggregatedTestResult { private static final AggregatedTestResult SKIPPED_RESULT = new AggregatedTestResult(SKIPPED, emptyList()); @@ -411,6 +444,125 @@ private static Type from(TestExecutionResult executionResult) { } return SUCCESS; } + + private static boolean isFailure(TestExecutionResult result) { + Optional throwable = result.getThrowable(); + return throwable.isPresent() && throwable.get() instanceof AssertionError; + } + } + } + + private static class ReplacingWriter extends Writer { + + private final Writer delegate; + private boolean whitespaceReplacingEnabled; + + ReplacingWriter(Writer delegate) { + this.delegate = delegate; + } + + void setWhitespaceReplacingEnabled(boolean whitespaceReplacingEnabled) { + this.whitespaceReplacingEnabled = whitespaceReplacingEnabled; + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + if (!whitespaceReplacingEnabled) { + delegate.write(cbuf, off, len); + return; + } + StringBuilder stringBuilder = new StringBuilder(len * 2); + for (int i = off; i < off + len; i++) { + char c = cbuf[i]; + String replacement = REPLACEMENTS_IN_ATTRIBUTE_VALUES.get(c); + if (replacement != null) { + stringBuilder.append(replacement); + } + else { + stringBuilder.append(c); + } + } + delegate.write(stringBuilder.toString()); + } + + @Override + public void write(int c) throws IOException { + if (whitespaceReplacingEnabled) { + super.write(c); + } + else { + delegate.write(c); + } + } + + @Override + public void write(char[] cbuf) throws IOException { + if (whitespaceReplacingEnabled) { + super.write(cbuf); + } + else { + delegate.write(cbuf); + } + } + + @Override + public void write(String str) throws IOException { + if (whitespaceReplacingEnabled) { + super.write(str); + } + else { + delegate.write(str); + } + } + + @Override + public void write(String str, int off, int len) throws IOException { + if (whitespaceReplacingEnabled) { + super.write(str, off, len); + } + else { + delegate.write(str, off, len); + } + } + + @Override + public Writer append(CharSequence csq) throws IOException { + if (whitespaceReplacingEnabled) { + return super.append(csq); + } + else { + return delegate.append(csq); + } + } + + @Override + public Writer append(CharSequence csq, int start, int end) throws IOException { + if (whitespaceReplacingEnabled) { + return super.append(csq, start, end); + } + else { + return delegate.append(csq, start, end); + } + } + + @Override + public Writer append(char c) throws IOException { + if (whitespaceReplacingEnabled) { + return super.append(c); + } + else { + return delegate.append(c); + } + } + + @Override + public void flush() throws IOException { + delegate.flush(); + } + + @Override + public void close() throws IOException { + delegate.close(); } } diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitContributor.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitContributor.java new file mode 100644 index 000000000000..ceff09855452 --- /dev/null +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitContributor.java @@ -0,0 +1,78 @@ +/* + * 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.reporting.open.xml; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.apiguardian.api.API.Status.INTERNAL; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apiguardian.api.API; +import org.opentest4j.reporting.schema.Namespace; +import org.opentest4j.reporting.tooling.spi.htmlreport.Contributor; +import org.opentest4j.reporting.tooling.spi.htmlreport.KeyValuePairs; +import org.opentest4j.reporting.tooling.spi.htmlreport.Section; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Contributes a section containing JUnit-specific metadata for each test node + * to the open-test-reporting HTML report. + * + * @since 1.12 + */ +@SuppressWarnings("exports") // we don't want to export 'org.opentest4j.reporting.tooling.spi' transitively +@API(status = INTERNAL, since = "1.12") +public class JUnitContributor implements Contributor { + + public JUnitContributor() { + } + + @Override + public List

contributeSectionsForTestNode(Context context) { + return findChild(context.element(), Namespace.REPORTING_CORE, "metadata") // + .map(metadata -> { + Map table = new LinkedHashMap<>(); + findChild(metadata, JUnitFactory.NAMESPACE, "type") // + .map(Node::getTextContent) // + .ifPresent(value -> table.put("Type", value)); + findChild(metadata, JUnitFactory.NAMESPACE, "uniqueId") // + .map(Node::getTextContent) // + .ifPresent(value -> table.put("Unique ID", value)); + findChild(metadata, JUnitFactory.NAMESPACE, "legacyReportingName") // + .map(Node::getTextContent) // + .ifPresent(value -> table.put("Legacy reporting name", value)); + return table; + }) // + .filter(table -> !table.isEmpty()) // + .map(table -> singletonList(Section.builder() // + .title("JUnit metadata") // + .order(15) // + .addBlock(KeyValuePairs.builder().content(table).build()) // + .build())) // + .orElse(emptyList()); + } + + private static Optional findChild(Node parent, Namespace namespace, String localName) { + NodeList children = parent.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (localName.equals(child.getLocalName()) && namespace.getUri().equals(child.getNamespaceURI())) { + return Optional.of(child); + } + } + return Optional.empty(); + } +} diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java index 8064da6f20c4..bb786f835830 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/JUnitFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java index 95ce6cac8d9a..902c0b3210d1 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/LegacyReportingName.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java index dbb5541e9ce3..954cdc334fc5 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,9 @@ package org.junit.platform.reporting.open.xml; import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.junit.platform.commons.util.StringUtils.isNotBlank; +import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; import static org.junit.platform.reporting.open.xml.JUnitFactory.legacyReportingName; import static org.junit.platform.reporting.open.xml.JUnitFactory.type; import static org.junit.platform.reporting.open.xml.JUnitFactory.uniqueId; @@ -18,11 +21,13 @@ import static org.opentest4j.reporting.events.core.CoreFactory.cpuCores; import static org.opentest4j.reporting.events.core.CoreFactory.data; import static org.opentest4j.reporting.events.core.CoreFactory.directorySource; +import static org.opentest4j.reporting.events.core.CoreFactory.file; import static org.opentest4j.reporting.events.core.CoreFactory.fileSource; import static org.opentest4j.reporting.events.core.CoreFactory.hostName; import static org.opentest4j.reporting.events.core.CoreFactory.infrastructure; import static org.opentest4j.reporting.events.core.CoreFactory.metadata; import static org.opentest4j.reporting.events.core.CoreFactory.operatingSystem; +import static org.opentest4j.reporting.events.core.CoreFactory.output; import static org.opentest4j.reporting.events.core.CoreFactory.reason; import static org.opentest4j.reporting.events.core.CoreFactory.result; import static org.opentest4j.reporting.events.core.CoreFactory.sources; @@ -30,6 +35,10 @@ import static org.opentest4j.reporting.events.core.CoreFactory.tags; import static org.opentest4j.reporting.events.core.CoreFactory.uriSource; import static org.opentest4j.reporting.events.core.CoreFactory.userName; +import static org.opentest4j.reporting.events.git.GitFactory.branch; +import static org.opentest4j.reporting.events.git.GitFactory.commit; +import static org.opentest4j.reporting.events.git.GitFactory.repository; +import static org.opentest4j.reporting.events.git.GitFactory.status; import static org.opentest4j.reporting.events.java.JavaFactory.classSource; import static org.opentest4j.reporting.events.java.JavaFactory.classpathResourceSource; import static org.opentest4j.reporting.events.java.JavaFactory.fileEncoding; @@ -42,23 +51,35 @@ import static org.opentest4j.reporting.events.root.RootFactory.reported; import static org.opentest4j.reporting.events.root.RootFactory.started; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.io.UncheckedIOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.charset.Charset; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Instant; +import java.time.LocalDateTime; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.ClassSource; import org.junit.platform.engine.support.descriptor.ClasspathResourceSource; @@ -71,9 +92,10 @@ import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; -import org.junit.platform.launcher.listeners.OutputDir; import org.opentest4j.reporting.events.api.DocumentWriter; import org.opentest4j.reporting.events.api.NamespaceRegistry; +import org.opentest4j.reporting.events.core.Attachments; +import org.opentest4j.reporting.events.core.Infrastructure; import org.opentest4j.reporting.events.core.Result; import org.opentest4j.reporting.events.core.Sources; import org.opentest4j.reporting.events.root.Events; @@ -88,13 +110,20 @@ public class OpenTestReportGeneratingListener implements TestExecutionListener { static final String ENABLED_PROPERTY_NAME = "junit.platform.reporting.open.xml.enabled"; - static final String OUTPUT_DIR_PROPERTY_NAME = "junit.platform.reporting.output.dir"; private final AtomicInteger idCounter = new AtomicInteger(); private final Map inProgressIds = new ConcurrentHashMap<>(); private DocumentWriter eventsFileWriter = DocumentWriter.noop(); + private final Path workingDir; + private Path outputDir; + @SuppressWarnings("unused") // Used via ServiceLoader public OpenTestReportGeneratingListener() { + this(Paths.get(".").toAbsolutePath()); + } + + OpenTestReportGeneratingListener(Path workingDir) { + this.workingDir = workingDir; } @Override @@ -103,12 +132,13 @@ public void testPlanExecutionStarted(TestPlan testPlan) { if (isEnabled(config)) { NamespaceRegistry namespaceRegistry = NamespaceRegistry.builder(Namespace.REPORTING_CORE) // .add("e", Namespace.REPORTING_EVENTS) // + .add("git", Namespace.REPORTING_GIT) // .add("java", Namespace.REPORTING_JAVA) // .add("junit", JUnitFactory.NAMESPACE, "https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd") // .build(); - Path eventsXml = OutputDir.create(config.get(OUTPUT_DIR_PROPERTY_NAME)) // - .createFile("junit-platform-events", "xml"); + outputDir = testPlan.getOutputDirectoryProvider().getRootDirectory(); + Path eventsXml = outputDir.resolve("open-test-report.xml"); try { eventsFileWriter = Events.createDocumentWriter(namespaceRegistry, eventsXml); reportInfrastructure(); @@ -138,9 +168,92 @@ private void reportInfrastructure() { .append(javaVersion(System.getProperty("java.version"))) // .append(fileEncoding(System.getProperty("file.encoding"))) // .append(heapSize(), heapSize -> heapSize.withMax(Runtime.getRuntime().maxMemory())); + + addGitInfo(infrastructure); }); } + private void addGitInfo(Infrastructure infrastructure) { + boolean gitInstalled = exec("git", "--version").isPresent(); + if (gitInstalled) { + exec("git", "config", "--get", "remote.origin.url") // + .filter(StringUtils::isNotBlank) // + .ifPresent( + gitUrl -> infrastructure.append(repository(), repository -> repository.withOriginUrl(gitUrl))); + exec("git", "rev-parse", "--abbrev-ref", "HEAD") // + .filter(StringUtils::isNotBlank) // + .ifPresent(branch -> infrastructure.append(branch(branch))); + exec("git", "rev-parse", "--verify", "HEAD") // + .filter(StringUtils::isNotBlank) // + .ifPresent(gitCommitHash -> infrastructure.append(commit(gitCommitHash))); + exec("git", "status", "--porcelain") // + .ifPresent(statusOutput -> infrastructure.append(status(statusOutput), + status -> status.withClean(statusOutput.isEmpty()))); + } + } + + Optional exec(String... args) { + + Process process = startProcess(args); + + try (Reader out = newBufferedReader(process.getInputStream()); + Reader err = newBufferedReader(process.getErrorStream())) { + + StringBuilder output = new StringBuilder(); + readAllChars(out, (chars, numChars) -> output.append(chars, 0, numChars)); + + readAllChars(err, (__, ___) -> { + // ignore + }); + + boolean terminated = process.waitFor(10, TimeUnit.SECONDS); + return terminated && process.exitValue() == 0 ? Optional.of(trimAtEnd(output)) : Optional.empty(); + } + catch (InterruptedException e) { + throw ExceptionUtils.throwAsUncheckedException(e); + } + catch (IOException ignore) { + return Optional.empty(); + } + finally { + process.destroyForcibly(); + } + } + + private static BufferedReader newBufferedReader(InputStream stream) { + return new BufferedReader(new InputStreamReader(stream, Charset.defaultCharset())); + } + + private Process startProcess(String[] command) { + Process process; + try { + process = new ProcessBuilder().directory(workingDir.toFile()).command(command).start(); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to start process", e); + } + return process; + } + + private static void readAllChars(Reader reader, BiConsumer consumer) throws IOException { + char[] buffer = new char[1024]; + int numChars; + while ((numChars = reader.read(buffer)) != -1) { + consumer.accept(buffer, numChars); + } + } + + private static String trimAtEnd(StringBuilder value) { + int endIndex = value.length(); + for (int i = value.length() - 1; i >= 0; i--) { + if (Character.isWhitespace(value.charAt(i))) { + endIndex--; + break; + } + } + return value.substring(0, endIndex); + } + @Override public void testPlanExecutionFinished(TestPlan testPlan) { try { @@ -160,7 +273,7 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) { reportStarted(testIdentifier, id); eventsFileWriter.append(finished(id, Instant.now()), // finished -> finished.append(result(Result.Status.SKIPPED), result -> { - if (StringUtils.isNotBlank(reason)) { + if (isNotBlank(reason)) { result.append(reason(reason)); } })); @@ -237,8 +350,37 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e String id = inProgressIds.get(testIdentifier.getUniqueIdObject()); eventsFileWriter.append(reported(id, Instant.now()), // reported -> reported.append(attachments(), // - attachments -> attachments.append(data(entry.getTimestamp()), // - data -> entry.getKeyValuePairs().forEach(data::addEntry)))); + attachments -> { + Map keyValuePairs = entry.getKeyValuePairs(); + if (keyValuePairs.containsKey(STDOUT_REPORT_ENTRY_KEY) + || keyValuePairs.containsKey(STDERR_REPORT_ENTRY_KEY)) { + attachOutput(attachments, entry.getTimestamp(), keyValuePairs.get(STDOUT_REPORT_ENTRY_KEY), + "stdout"); + attachOutput(attachments, entry.getTimestamp(), keyValuePairs.get(STDERR_REPORT_ENTRY_KEY), + "stderr"); + } + else { + attachments.append(data(entry.getTimestamp()), // + data -> keyValuePairs.forEach(data::addEntry)); + } + })); + } + + private static void attachOutput(Attachments attachments, LocalDateTime timestamp, String content, String source) { + if (content != null) { + attachments.append(output(timestamp), output -> output.withSource(source).withContent(content)); + } + } + + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry entry) { + String id = inProgressIds.get(testIdentifier.getUniqueIdObject()); + eventsFileWriter.append(reported(id, Instant.now()), // + reported -> reported.append(attachments(), attachments -> attachments.append(file(entry.getTimestamp()), // + file -> { + file.withPath(outputDir.relativize(entry.getPath()).toString()); + entry.getMediaType().ifPresent(file::withMediaType); + }))); } @Override diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java index ca08eef6fad8..42e31aa7ac1a 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/Type.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java index c1ac3bbce988..259e78faa164 100644 --- a/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java +++ b/junit-platform-reporting/src/main/java/org/junit/platform/reporting/open/xml/UniqueId.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-reporting/src/main/resources/META-INF/services/org.opentest4j.reporting.tooling.spi.htmlreport.Contributor b/junit-platform-reporting/src/main/resources/META-INF/services/org.opentest4j.reporting.tooling.spi.htmlreport.Contributor new file mode 100644 index 000000000000..151ef99b166d --- /dev/null +++ b/junit-platform-reporting/src/main/resources/META-INF/services/org.opentest4j.reporting.tooling.spi.htmlreport.Contributor @@ -0,0 +1 @@ +org.junit.platform.reporting.open.xml.JUnitContributor diff --git a/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java b/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java index 66c749470bf0..f1fe8cd51dca 100644 --- a/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java +++ b/junit-platform-reporting/src/module/org.junit.platform.reporting/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 @@ -19,6 +19,7 @@ requires org.junit.platform.commons; requires transitive org.junit.platform.engine; requires transitive org.junit.platform.launcher; + requires org.opentest4j.reporting.tooling.spi; // exports org.junit.platform.reporting; empty package exports org.junit.platform.reporting.legacy; @@ -27,4 +28,7 @@ provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener; + + provides org.opentest4j.reporting.tooling.spi.htmlreport.Contributor + with org.junit.platform.reporting.open.xml.JUnitContributor; } diff --git a/junit-platform-reporting/src/nativeImage/initialize-at-build-time b/junit-platform-reporting/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..1b4f355f53cf --- /dev/null +++ b/junit-platform-reporting/src/nativeImage/initialize-at-build-time @@ -0,0 +1,2 @@ +org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener +org.junit.platform.reporting.shadow.org.opentest4j.reporting.events.api.DocumentWriter$1 diff --git a/junit-platform-reporting/src/test/README.md b/junit-platform-reporting/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-reporting/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/open/xml/OpenTestReportGenerationSystemPropertyOverride.java b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/open/xml/OpenTestReportGenerationSystemPropertyOverride.java new file mode 100644 index 000000000000..8c9faca9bea8 --- /dev/null +++ b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/open/xml/OpenTestReportGenerationSystemPropertyOverride.java @@ -0,0 +1,43 @@ +/* + * 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.reporting.open.xml; + +import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class OpenTestReportGenerationSystemPropertyOverride + implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + @Override + public void beforeTestExecution(ExtensionContext context) { + var oldValue = System.clearProperty(ENABLED_PROPERTY_NAME); + getStore(context).put(ENABLED_PROPERTY_NAME, oldValue); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + var oldValue = getStore(context).get(ENABLED_PROPERTY_NAME, String.class); + if (oldValue == null) { + System.clearProperty(ENABLED_PROPERTY_NAME); + } + else { + System.setProperty(ENABLED_PROPERTY_NAME, oldValue); + } + } + + private static ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore( + ExtensionContext.Namespace.create(OpenTestReportGenerationSystemPropertyOverride.class)); + } +} diff --git a/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java new file mode 100644 index 000000000000..04228a9aa1fb --- /dev/null +++ b/junit-platform-reporting/src/testFixtures/java/org/junit/platform/reporting/testutil/FileUtils.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.reporting.testutil; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileUtils { + + public static Path findPath(Path rootDir, String syntaxAndPattern) { + var matcher = rootDir.getFileSystem().getPathMatcher(syntaxAndPattern); + try (var files = Files.walk(rootDir)) { + return files.filter(matcher::matches).findFirst() // + .orElseThrow(() -> new AssertionError( + "Failed to find file matching '%s' in %s".formatted(syntaxAndPattern, rootDir))); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/junit-platform-reporting/src/testFixtures/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/junit-platform-reporting/src/testFixtures/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 000000000000..1b5725ecf2df --- /dev/null +++ b/junit-platform-reporting/src/testFixtures/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.junit.platform.reporting.open.xml.OpenTestReportGenerationSystemPropertyOverride diff --git a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java index 8b8c26f098b1..0df3bbb19ef3 100644 --- a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java +++ b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatform.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -160,6 +160,7 @@ private JUnitPlatformTestTree generateTestTree(LauncherDiscoveryRequest discover return new JUnitPlatformTestTree(testPlan, this.testClass); } + @SuppressWarnings("deprecation") private LauncherDiscoveryRequest createDiscoveryRequest() { SuiteLauncherDiscoveryRequestBuilder requestBuilder = request(); // Allows @RunWith(JUnitPlatform.class) to be added to any test case diff --git a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java index e91045ee8422..180472e20bb6 100644 --- a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java +++ b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformRunnerListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; @@ -85,6 +86,11 @@ public void reportingEntryPublished(TestIdentifier testIdentifier, ReportEntry e System.out.println(entry); } + @Override + public void fileEntryPublished(TestIdentifier testIdentifier, FileEntry file) { + System.out.println(file); + } + private Failure toFailure(TestExecutionResult testExecutionResult, Description description) { return new Failure(description, testExecutionResult.getThrowable().orElse(null)); } diff --git a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java index 96f7239f9227..e76f2d56a9be 100644 --- a/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java +++ b/junit-platform-runner/src/main/java/org/junit/platform/runner/JUnitPlatformTestTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,7 +22,7 @@ import java.util.function.Function; import java.util.function.Predicate; -import org.junit.platform.commons.util.AnnotationUtils; +import org.junit.platform.commons.support.AnnotationSupport; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.TestSource; import org.junit.platform.engine.UniqueId; @@ -76,7 +76,7 @@ private Description generateSuiteDescription(TestPlan testPlan, Class testCla private String getSuiteDisplayName(Class testClass) { // @formatter:off - return AnnotationUtils.findAnnotation(testClass, SuiteDisplayName.class) + return AnnotationSupport.findAnnotation(testClass, SuiteDisplayName.class) .map(SuiteDisplayName::value) .filter(StringUtils::isNotBlank) .orElse(testClass.getName()); diff --git a/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java b/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java index 47edeb10e7fd..e9dc34ed78d1 100644 --- a/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java +++ b/junit-platform-runner/src/module/org.junit.platform.runner/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-runner/src/test/README.md b/junit-platform-runner/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-runner/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/AfterSuite.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/AfterSuite.java new file mode 100644 index 000000000000..77ff31333f1a --- /dev/null +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/AfterSuite.java @@ -0,0 +1,75 @@ +/* + * 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.suite.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @AfterSuite} is used to signal that the annotated method should be + * executed after all tests in the current test suite. + * + *

Method Signatures

+ * + *

{@code @AfterSuite} methods must have a {@code void} return type, must + * be {@code static} and must not be {@code private}. + * + *

Inheritance and Execution Order

+ * + *

{@code @AfterSuite} methods are inherited from superclasses as long as they + * are not overridden according to the visibility rules of the Java + * language. Furthermore, {@code @AfterSuite} methods from superclasses will be + * executed after {@code @AfterSuite} methods in subclasses. + * + *

The JUnit Platform Suite Engine does not guarantee the execution order of + * multiple {@code @AfterSuite} methods that are declared within a single test + * class or test interface. While it may at times appear that these methods are + * invoked in alphabetical order, they are in fact sorted using an algorithm + * that is deterministic but intentionally non-obvious. + * + *

In addition, {@code @AfterSuite} methods are in no way linked to + * {@code @BeforeSuite} methods. Consequently, there are no guarantees with regard + * to their wrapping behavior. For example, given two + * {@code @BeforeSuite} methods {@code createA()} and {@code createB()} as well as + * two {@code @AfterSuite} methods {@code destroyA()} and {@code destroyB()}, the + * order in which the {@code @BeforeSuite} methods are executed (e.g. + * {@code createA()} before {@code createB()}) does not imply any order for the + * seemingly corresponding {@code @AfterSuite} methods. In other words, + * {@code destroyA()} might be called before or after + * {@code destroyB()}. The JUnit Team therefore recommends that developers + * declare at most one {@code @BeforeSuite} method and at most one + * {@code @AfterSuite} method per test class or test interface unless there are no + * dependencies between the {@code @BeforeSuite} methods or between the + * {@code @AfterSuite} methods. + * + *

Composition

+ * + *

{@code @AfterSuite} may be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @AfterSuite}. + * + * @since 1.11 + * @see BeforeSuite + * @see Suite + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = EXPERIMENTAL, since = "1.11") +public @interface AfterSuite { +} diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/BeforeSuite.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/BeforeSuite.java new file mode 100644 index 000000000000..c62d9ccdc040 --- /dev/null +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/BeforeSuite.java @@ -0,0 +1,75 @@ +/* + * 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.suite.api; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apiguardian.api.API; + +/** + * {@code @BeforeSuite} is used to signal that the annotated method should be + * executed before all tests in the current test suite. + * + *

Method Signatures

+ * + *

{@code @BeforeSuite} methods must have a {@code void} return type, must + * be {@code static} and must not be {@code private}. + * + *

Inheritance and Execution Order

+ * + *

{@code @BeforeSuite} methods are inherited from superclasses as long as they + * are not overridden according to the visibility rules of the Java + * language. Furthermore, {@code @BeforeSuite} methods from superclasses will be + * executed before {@code @BeforeSuite} methods in subclasses. + * + *

The JUnit Platform Suite Engine does not guarantee the execution order of + * multiple {@code @BeforeSuite} methods that are declared within a single test + * class or test interface. While it may at times appear that these methods are + * invoked in alphabetical order, they are in fact sorted using an algorithm + * that is deterministic but intentionally non-obvious. + * + *

In addition, {@code @BeforeSuite} methods are in no way linked to + * {@code @AfterSuite} methods. Consequently, there are no guarantees with regard + * to their wrapping behavior. For example, given two + * {@code @BeforeSuite} methods {@code createA()} and {@code createB()} as well as + * two {@code @AfterSuite} methods {@code destroyA()} and {@code destroyB()}, the + * order in which the {@code @BeforeSuite} methods are executed (e.g. + * {@code createA()} before {@code createB()}) does not imply any order for the + * seemingly corresponding {@code @AfterSuite} methods. In other words, + * {@code destroyA()} might be called before or after + * {@code destroyB()}. The JUnit Team therefore recommends that developers + * declare at most one {@code @BeforeSuite} method and at most one + * {@code @AfterSuite} method per test class or test interface unless there are no + * dependencies between the {@code @BeforeSuite} methods or between the + * {@code @AfterSuite} methods. + * + *

Composition

+ * + *

{@code @BeforeSuite} may be used as a meta-annotation in order to create + * a custom composed annotation that inherits the semantics of + * {@code @BeforeSuite}. + * + * @since 1.11 + * @see AfterSuite + * @see Suite + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@API(status = EXPERIMENTAL, since = "1.11") +public @interface BeforeSuite { +} diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java index 268f7029cc12..0a8d010a3624 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java index 9c7514a1315b..2deb5286eb68 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResource.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResource.java index 86fdfb38a71f..6e370f69ff4c 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResource.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResources.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResources.java index 6b6fd1f6790e..0897e77b4622 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResources.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ConfigurationParametersResources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java index 84905807c45c..2f059ac7cf5c 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/DisableParentConfigurationParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java index a68525a4a09b..9e8ee74c28ef 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeClassNamePatterns.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java index 1925e2b3d612..f6c2ac53f4dc 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeEngines.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java index 196f6ab156c7..5335f7c0e844 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludePackages.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java index 695e68cdb9e1..f24bc12ae6e7 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/ExcludeTags.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java index d3d962f1ffb1..313f52c9d371 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeClassNamePatterns.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java index b7014e0d39fa..f35461d438fd 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeEngines.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java index d3191f255e07..f634544a7b84 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludePackages.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java index 98cf52e50a38..12110bee9bfc 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/IncludeTags.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Select.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Select.java index 79c991743f5e..f2fd4a47a370 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Select.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Select.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java index 13aa76cf0cf9..9964f7b68b68 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasses.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java index 1705163ad9aa..c3255f501c39 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java index a643fbfc0415..26f80d906716 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectClasspathResources.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java index c08747e5a9fa..57ea6bdbbe39 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectDirectories.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java index 0df68a8ca0b4..7359febbda84 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java index 53875f94a666..06ebf7ebaf48 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectFiles.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java index 7be2264c8a0d..c1fa0f6dc40d 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethods.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethods.java index 6f487107856d..f3583156f325 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethods.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java index 5b003444629a..e0dccf332c18 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectModules.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java index 592fe107af56..bba47a24c2d9 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectPackages.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java index f3f0bb8e7398..a281678df0a2 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SelectUris.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Selects.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Selects.java index 44403f81aa21..63f52143916b 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Selects.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Selects.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java index 35e0d2f2140f..c44c5d9a6bc8 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/Suite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java index 0db93a88eedf..0113b1827b44 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/SuiteDisplayName.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java index d9621689172e..749fcdb13a9f 100644 --- a/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java +++ b/junit-platform-suite-api/src/main/java/org/junit/platform/suite/api/UseTechnicalNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java b/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java index 9af8daea9d71..9d8df8da3cf0 100644 --- a/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java +++ b/junit-platform-suite-api/src/module/org.junit.platform.suite.api/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-suite-api/src/test/README.md b/junit-platform-suite-api/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-suite-api/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java index a66a5545de54..969fe04e9376 100644 --- a/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java +++ b/junit-platform-suite-commons/src/main/java/org/junit/platform/suite/commons/AdditionalDiscoverySelectors.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 23eecf29ce62..ccee1edb4eb8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,8 +11,8 @@ package org.junit.platform.suite.commons; import static java.util.stream.Collectors.toList; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectClasspathResource; import static org.junit.platform.suite.commons.AdditionalDiscoverySelectors.selectFile; @@ -41,6 +41,7 @@ import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.discovery.MethodSelector; import org.junit.platform.engine.discovery.PackageNameFilter; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.EngineFilter; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TagFilter; @@ -261,6 +262,12 @@ public SuiteLauncherDiscoveryRequestBuilder enableImplicitConfigurationParameter return this; } + public SuiteLauncherDiscoveryRequestBuilder outputDirectoryProvider( + OutputDirectoryProvider outputDirectoryProvider) { + delegate.outputDirectoryProvider(outputDirectoryProvider); + return this; + } + /** * Apply a suite's annotation-based configuration, selectors, and filters to * this builder. @@ -331,6 +338,7 @@ public SuiteLauncherDiscoveryRequestBuilder applyConfigurationParametersFromSuit *

  • {@link SelectModules}
  • *
  • {@link SelectUris}
  • *
  • {@link SelectPackages}
  • + *
  • {@link Select}
  • * * * @param suiteClass the class to apply the discovery selectors and filter @@ -342,71 +350,84 @@ public SuiteLauncherDiscoveryRequestBuilder applyConfigurationParametersFromSuit public SuiteLauncherDiscoveryRequestBuilder applySelectorsAndFiltersFromSuite(Class suiteClass) { Preconditions.notNull(suiteClass, "Suite class must not be null"); - // Annotations in alphabetical order (except @SelectClasses) - // @formatter:off - findAnnotationValues(suiteClass, ExcludeClassNamePatterns.class, ExcludeClassNamePatterns::value) - .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) - .map(ClassNameFilter::excludeClassNamePatterns) + addExcludeFilters(suiteClass); + // Process @SelectClasses and @SelectMethod before @IncludeClassNamePatterns, since the names + // of selected classes get automatically added to the include filter. + addClassAndMethodSelectors(suiteClass); + addIncludeFilters(suiteClass); + addOtherSelectors(suiteClass); + return this; + } + + private void addExcludeFilters(Class suiteClass) { + findAnnotationValues(suiteClass, ExcludeClassNamePatterns.class, ExcludeClassNamePatterns::value) // + .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) // + .map(ClassNameFilter::excludeClassNamePatterns) // .ifPresent(this::filters); - findAnnotationValues(suiteClass, ExcludeEngines.class, ExcludeEngines::value) - .map(EngineFilter::excludeEngines) + findAnnotationValues(suiteClass, ExcludeEngines.class, ExcludeEngines::value) // + .map(EngineFilter::excludeEngines) // .ifPresent(this::filters); - findAnnotationValues(suiteClass, ExcludePackages.class, ExcludePackages::value) - .map(PackageNameFilter::excludePackageNames) + findAnnotationValues(suiteClass, ExcludePackages.class, ExcludePackages::value) // + .map(PackageNameFilter::excludePackageNames) // .ifPresent(this::filters); - findAnnotationValues(suiteClass, ExcludeTags.class, ExcludeTags::value) - .map(TagFilter::excludeTags) + findAnnotationValues(suiteClass, ExcludeTags.class, ExcludeTags::value) // + .map(TagFilter::excludeTags) // .ifPresent(this::filters); - // Process @SelectClasses before @IncludeClassNamePatterns, since the names - // of selected classes get automatically added to the include filter. - findAnnotation(suiteClass, SelectClasses.class) - .map(annotation -> selectClasses(suiteClass, annotation)) + } + + private void addClassAndMethodSelectors(Class suiteClass) { + findAnnotation(suiteClass, SelectClasses.class) // + .map(annotation -> selectClasses(suiteClass, annotation)) // .ifPresent(this::selectors); - findRepeatableAnnotations(suiteClass, SelectMethod.class) - .stream() - .map(annotation -> selectMethod(suiteClass, annotation)) + findRepeatableAnnotations(suiteClass, SelectMethod.class) // + .stream() // + .map(annotation -> selectMethod(suiteClass, annotation)) // .forEach(this::selectors); - findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value) - .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) - .map(this::createIncludeClassNameFilter) + } + + private void addIncludeFilters(Class suiteClass) { + findAnnotationValues(suiteClass, IncludeClassNamePatterns.class, IncludeClassNamePatterns::value) // + .flatMap(SuiteLauncherDiscoveryRequestBuilder::trimmed) // + .map(this::createIncludeClassNameFilter) // .ifPresent(filters -> { this.includeClassNamePatternsUsed = true; filters(filters); }); - findAnnotationValues(suiteClass, IncludeEngines.class, IncludeEngines::value) - .map(EngineFilter::includeEngines) + findAnnotationValues(suiteClass, IncludeEngines.class, IncludeEngines::value) // + .map(EngineFilter::includeEngines) // .ifPresent(this::filters); - findAnnotationValues(suiteClass, IncludePackages.class, IncludePackages::value) - .map(PackageNameFilter::includePackageNames) + findAnnotationValues(suiteClass, IncludePackages.class, IncludePackages::value) // + .map(PackageNameFilter::includePackageNames) // .ifPresent(this::filters); - findAnnotationValues(suiteClass, IncludeTags.class, IncludeTags::value) - .map(TagFilter::includeTags) + findAnnotationValues(suiteClass, IncludeTags.class, IncludeTags::value) // + .map(TagFilter::includeTags) // .ifPresent(this::filters); - findRepeatableAnnotations(suiteClass, SelectClasspathResource.class) - .stream() - .map(annotation -> selectClasspathResource(annotation.value(), annotation.line(), annotation.column())) + } + + private void addOtherSelectors(Class suiteClass) { + findRepeatableAnnotations(suiteClass, SelectClasspathResource.class) // + .stream() // + .map(annotation -> selectClasspathResource(annotation.value(), annotation.line(), annotation.column())) // .forEach(this::selectors); - findAnnotationValues(suiteClass, SelectDirectories.class, SelectDirectories::value) - .map(AdditionalDiscoverySelectors::selectDirectories) + findAnnotationValues(suiteClass, SelectDirectories.class, SelectDirectories::value) // + .map(AdditionalDiscoverySelectors::selectDirectories) // .ifPresent(this::selectors); - findRepeatableAnnotations(suiteClass, SelectFile.class) - .stream() - .map(annotation -> selectFile(annotation.value(), annotation.line(), annotation.column())) + findRepeatableAnnotations(suiteClass, SelectFile.class) // + .stream() // + .map(annotation -> selectFile(annotation.value(), annotation.line(), annotation.column())) // .forEach(this::selectors); - findAnnotationValues(suiteClass, SelectModules.class, SelectModules::value) - .map(AdditionalDiscoverySelectors::selectModules) + findAnnotationValues(suiteClass, SelectModules.class, SelectModules::value) // + .map(AdditionalDiscoverySelectors::selectModules) // .ifPresent(this::selectors); - findAnnotationValues(suiteClass, SelectUris.class, SelectUris::value) - .map(AdditionalDiscoverySelectors::selectUris) + findAnnotationValues(suiteClass, SelectUris.class, SelectUris::value) // + .map(AdditionalDiscoverySelectors::selectUris) // .ifPresent(this::selectors); - findAnnotationValues(suiteClass, SelectPackages.class, SelectPackages::value) - .map(AdditionalDiscoverySelectors::selectPackages) + findAnnotationValues(suiteClass, SelectPackages.class, SelectPackages::value) // + .map(AdditionalDiscoverySelectors::selectPackages) // .ifPresent(this::selectors); - findAnnotationValues(suiteClass, Select.class, Select::value) - .map(AdditionalDiscoverySelectors::parseIdentifiers) + findAnnotationValues(suiteClass, Select.class, Select::value) // + .map(AdditionalDiscoverySelectors::parseIdentifiers) // .ifPresent(this::selectors); - // @formatter:on - return this; } /** @@ -450,23 +471,7 @@ private MethodSelector selectMethod(Class suiteClass, SelectMethod annotation private MethodSelector toMethodSelector(Class suiteClass, SelectMethod annotation) { if (!annotation.value().isEmpty()) { - Preconditions.condition(annotation.type() == Class.class, - () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, - "type must not be set in conjunction with fully qualified method name")); - Preconditions.condition(annotation.typeName().isEmpty(), - () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, - "type name must not be set in conjunction with fully qualified method name")); - Preconditions.condition(annotation.name().isEmpty(), - () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, - "method name must not be set in conjunction with fully qualified method name")); - Preconditions.condition(annotation.parameterTypes().length == 0, - () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, - "parameter types must not be set in conjunction with fully qualified method name")); - Preconditions.condition(annotation.parameterTypeNames().isEmpty(), - () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, - "parameter type names must not be set in conjunction with fully qualified method name")); - - return DiscoverySelectors.selectMethod(annotation.value()); + return toMethodSelectorFromFQMN(suiteClass, annotation); } Class type = annotation.type() == Class.class ? null : annotation.type(); @@ -480,25 +485,44 @@ private MethodSelector toMethodSelector(Class suiteClass, SelectMethod annota () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "either parameter type names or parameter types must be set but not both")); } + return toMethodSelector(suiteClass, type, typeName, parameterTypes, methodName, parameterTypeNames); + } + + private static MethodSelector toMethodSelectorFromFQMN(Class suiteClass, SelectMethod annotation) { + Preconditions.condition(annotation.type() == Class.class, + () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, + "type must not be set in conjunction with fully qualified method name")); + Preconditions.condition(annotation.typeName().isEmpty(), + () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, + "type name must not be set in conjunction with fully qualified method name")); + Preconditions.condition(annotation.name().isEmpty(), + () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, + "method name must not be set in conjunction with fully qualified method name")); + Preconditions.condition(annotation.parameterTypes().length == 0, + () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, + "parameter types must not be set in conjunction with fully qualified method name")); + Preconditions.condition(annotation.parameterTypeNames().isEmpty(), + () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, + "parameter type names must not be set in conjunction with fully qualified method name")); + + return DiscoverySelectors.selectMethod(annotation.value()); + } + + private static MethodSelector toMethodSelector(Class suiteClass, Class type, String typeName, + Class[] parameterTypes, String methodName, String parameterTypeNames) { if (type == null) { Preconditions.notBlank(typeName, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "type must be set or type name must not be blank")); - if (parameterTypes == null) { - return DiscoverySelectors.selectMethod(typeName, methodName, parameterTypeNames); - } - else { - return DiscoverySelectors.selectMethod(typeName, methodName, parameterTypes); - } + return parameterTypes == null // + ? DiscoverySelectors.selectMethod(typeName, methodName, parameterTypeNames) // + : DiscoverySelectors.selectMethod(typeName, methodName, parameterTypes); } else { Preconditions.condition(typeName == null, () -> prefixErrorMessageForInvalidSelectMethodUsage(suiteClass, "either type name or type must be set but not both")); - if (parameterTypes == null) { - return DiscoverySelectors.selectMethod(type, methodName, parameterTypeNames); - } - else { - return DiscoverySelectors.selectMethod(type, methodName, parameterTypes); - } + return parameterTypes == null // + ? DiscoverySelectors.selectMethod(type, methodName, parameterTypeNames) // + : DiscoverySelectors.selectMethod(type, methodName, parameterTypes); } } diff --git a/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java b/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java index b810efc7f0ec..62c6732235d3 100644 --- a/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java +++ b/junit-platform-suite-commons/src/module/org.junit.platform.suite.commons/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * 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 @@ -9,7 +9,7 @@ */ /** - * Common support utilities for declarative test suite executors. + * Common support utilities for declarative test suites. * * @since 1.8 */ diff --git a/junit-platform-suite-commons/src/test/README.md b/junit-platform-suite-commons/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-suite-commons/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts index 36abcdbc088d..72f90de35321 100644 --- a/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts +++ b/junit-platform-suite-engine/junit-platform-suite-engine.gradle.kts @@ -1,5 +1,6 @@ plugins { id("junitbuild.java-library-conventions") + id("junitbuild.native-image-properties") } description = "JUnit Platform Suite Engine" diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java index 153a671b317d..1aea480e9569 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/ClassSelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,13 +18,14 @@ import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId.Segment; import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.UniqueIdSelector; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.engine.support.discovery.SelectorResolver; /** @@ -39,12 +40,14 @@ final class ClassSelectorResolver implements SelectorResolver { private final Predicate classNameFilter; private final SuiteEngineDescriptor suiteEngineDescriptor; private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; ClassSelectorResolver(Predicate classNameFilter, SuiteEngineDescriptor suiteEngineDescriptor, - ConfigurationParameters configurationParameters) { + ConfigurationParameters configurationParameters, OutputDirectoryProvider outputDirectoryProvider) { this.classNameFilter = classNameFilter; this.suiteEngineDescriptor = suiteEngineDescriptor; this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; } @Override @@ -89,7 +92,7 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { } private static Optional> tryLoadSuiteClass(UniqueId.Segment segment) { - return ReflectionUtils.tryToLoadClass(segment.getValue()).toOptional(); + return ReflectionSupport.tryToLoadClass(segment.getValue()).toOptional(); } private static Resolution toResolution(Optional suite) { @@ -103,7 +106,7 @@ private Optional newSuiteDescriptor(Class suiteClass, Te return Optional.empty(); } - return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters)); + return Optional.of(new SuiteTestDescriptor(id, suiteClass, configurationParameters, outputDirectoryProvider)); } private static boolean containsCycle(UniqueId id) { diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java index 15d31d65ccc2..3a9775ea5e9a 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/DiscoverySelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -25,7 +25,8 @@ final class DiscoverySelectorResolver { .addSelectorResolver(context -> new ClassSelectorResolver( context.getClassNameFilter(), context.getEngineDescriptor(), - context.getDiscoveryRequest().getConfigurationParameters())) + context.getDiscoveryRequest().getConfigurationParameters(), + context.getDiscoveryRequest().getOutputDirectoryProvider())) .build(); // @formatter:on diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java index f079cf8162bf..ebfa4cc50e05 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsPotentialTestContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,9 +10,9 @@ package org.junit.platform.suite.engine; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isPrivate; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPrivate; import java.util.function.Predicate; diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java index 6a162075a719..97947eefc831 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/IsSuiteClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/LifecycleMethodUtils.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/LifecycleMethodUtils.java new file mode 100644 index 000000000000..dcf971ce86ad --- /dev/null +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/LifecycleMethodUtils.java @@ -0,0 +1,90 @@ +/* + * 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.suite.engine; + +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods; +import static org.junit.platform.commons.util.ReflectionUtils.returnsPrimitiveVoid; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; + +import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; +import org.junit.platform.suite.api.AfterSuite; +import org.junit.platform.suite.api.BeforeSuite; + +/** + * Collection of utilities for working with test lifecycle methods. + * + * @since 1.11 + */ +final class LifecycleMethodUtils { + + private LifecycleMethodUtils() { + /* no-op */ + } + + static List findBeforeSuiteMethods(Class testClass, ThrowableCollector throwableCollector) { + return findMethodsAndAssertStaticAndNonPrivate(testClass, BeforeSuite.class, HierarchyTraversalMode.TOP_DOWN, + throwableCollector); + } + + static List findAfterSuiteMethods(Class testClass, ThrowableCollector throwableCollector) { + return findMethodsAndAssertStaticAndNonPrivate(testClass, AfterSuite.class, HierarchyTraversalMode.BOTTOM_UP, + throwableCollector); + } + + private static List findMethodsAndAssertStaticAndNonPrivate(Class testClass, + Class annotationType, HierarchyTraversalMode traversalMode, + ThrowableCollector throwableCollector) { + + List methods = findAnnotatedMethods(testClass, annotationType, traversalMode); + throwableCollector.execute(() -> methods.forEach(method -> { + assertVoid(annotationType, method); + assertStatic(annotationType, method); + assertNonPrivate(annotationType, method); + assertNoParameters(annotationType, method); + })); + return methods; + } + + private static void assertStatic(Class annotationType, Method method) { + if (ModifierSupport.isNotStatic(method)) { + throw new JUnitException(String.format("@%s method '%s' must be static.", annotationType.getSimpleName(), + method.toGenericString())); + } + } + + private static void assertNonPrivate(Class annotationType, Method method) { + if (ModifierSupport.isPrivate(method)) { + throw new JUnitException(String.format("@%s method '%s' must not be private.", + annotationType.getSimpleName(), method.toGenericString())); + } + } + + private static void assertVoid(Class annotationType, Method method) { + if (!returnsPrimitiveVoid(method)) { + throw new JUnitException(String.format("@%s method '%s' must not return a value.", + annotationType.getSimpleName(), method.toGenericString())); + } + } + + private static void assertNoParameters(Class annotationType, Method method) { + if (method.getParameterCount() > 0) { + throw new JUnitException(String.format("@%s method '%s' must not accept parameters.", + annotationType.getSimpleName(), method.toGenericString())); + } + } + +} diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java index 2c4db7af1bb3..e0f22d8fa257 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/NoTestsDiscoveredException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java index b70aae700fd7..12c038bccf1b 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteEngineDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 e2932f3bd4b1..c3a62006e859 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java index 9fa107cb5f2c..bfcd2f3b541f 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,14 @@ package org.junit.platform.suite.engine; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import static org.junit.platform.suite.commons.SuiteLauncherDiscoveryRequestBuilder.request; +import java.lang.reflect.Method; +import java.util.List; + import org.junit.platform.commons.JUnitException; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.Preconditions; import org.junit.platform.commons.util.StringUtils; import org.junit.platform.engine.ConfigurationParameters; @@ -22,8 +26,11 @@ import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; +import org.junit.platform.engine.support.hierarchical.ThrowableCollector; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryResult; import org.junit.platform.launcher.listeners.TestExecutionSummary; @@ -47,15 +54,18 @@ final class SuiteTestDescriptor extends AbstractTestDescriptor { private final SuiteLauncherDiscoveryRequestBuilder discoveryRequestBuilder = request(); private final ConfigurationParameters configurationParameters; + private final OutputDirectoryProvider outputDirectoryProvider; private final Boolean failIfNoTests; private final Class suiteClass; private LauncherDiscoveryResult launcherDiscoveryResult; private SuiteLauncher launcher; - SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters) { + SuiteTestDescriptor(UniqueId id, Class suiteClass, ConfigurationParameters configurationParameters, + OutputDirectoryProvider outputDirectoryProvider) { super(id, getSuiteDisplayName(suiteClass), ClassSource.from(suiteClass)); this.configurationParameters = configurationParameters; + this.outputDirectoryProvider = outputDirectoryProvider; this.failIfNoTests = getFailIfNoTests(suiteClass); this.suiteClass = suiteClass; } @@ -93,6 +103,7 @@ void discover() { .enableImplicitConfigurationParameters(false) .parentConfigurationParameters(configurationParameters) .applyConfigurationParametersFromSuite(suiteClass) + .outputDirectoryProvider(outputDirectoryProvider) .build(); // @formatter:on this.launcher = SuiteLauncher.create(); @@ -121,16 +132,58 @@ private static String getSuiteDisplayName(Class testClass) { void execute(EngineExecutionListener parentEngineExecutionListener) { parentEngineExecutionListener.executionStarted(this); + ThrowableCollector throwableCollector = new OpenTest4JAwareThrowableCollector(); + + List beforeSuiteMethods = LifecycleMethodUtils.findBeforeSuiteMethods(suiteClass, throwableCollector); + List afterSuiteMethods = LifecycleMethodUtils.findAfterSuiteMethods(suiteClass, throwableCollector); + + executeBeforeSuiteMethods(beforeSuiteMethods, throwableCollector); + + TestExecutionSummary summary = executeTests(parentEngineExecutionListener, throwableCollector); + + executeAfterSuiteMethods(afterSuiteMethods, throwableCollector); + + TestExecutionResult testExecutionResult = computeTestExecutionResult(summary, throwableCollector); + parentEngineExecutionListener.executionFinished(this, testExecutionResult); + } + + private void executeBeforeSuiteMethods(List beforeSuiteMethods, ThrowableCollector throwableCollector) { + if (throwableCollector.isNotEmpty()) { + return; + } + for (Method beforeSuiteMethod : beforeSuiteMethods) { + throwableCollector.execute(() -> ReflectionSupport.invokeMethod(beforeSuiteMethod, null)); + if (throwableCollector.isNotEmpty()) { + return; + } + } + } + + private TestExecutionSummary executeTests(EngineExecutionListener parentEngineExecutionListener, + ThrowableCollector throwableCollector) { + if (throwableCollector.isNotEmpty()) { + return null; + } + // #2838: The discovery result from a suite may have been filtered by // post discovery filters from the launcher. The discovery result should // be pruned accordingly. LauncherDiscoveryResult discoveryResult = this.launcherDiscoveryResult.withRetainedEngines( getChildren()::contains); - TestExecutionSummary summary = launcher.execute(discoveryResult, parentEngineExecutionListener); - parentEngineExecutionListener.executionFinished(this, computeTestExecutionResult(summary)); + return launcher.execute(discoveryResult, parentEngineExecutionListener); } - private TestExecutionResult computeTestExecutionResult(TestExecutionSummary summary) { + private void executeAfterSuiteMethods(List afterSuiteMethods, ThrowableCollector throwableCollector) { + for (Method afterSuiteMethod : afterSuiteMethods) { + throwableCollector.execute(() -> ReflectionSupport.invokeMethod(afterSuiteMethod, null)); + } + } + + private TestExecutionResult computeTestExecutionResult(TestExecutionSummary summary, + ThrowableCollector throwableCollector) { + if (throwableCollector.isNotEmpty()) { + return TestExecutionResult.failed(throwableCollector.getThrowable()); + } if (failIfNoTests && summary.getTestsFoundCount() == 0) { return TestExecutionResult.failed(new NoTestsDiscoveredException(suiteClass)); } diff --git a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java index b489870ba488..c0f754639c80 100644 --- a/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java +++ b/junit-platform-suite-engine/src/main/java/org/junit/platform/suite/engine/SuiteTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java b/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java index f98845cb91ff..e8a43bf80e1c 100644 --- a/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java +++ b/junit-platform-suite-engine/src/module/org.junit.platform.suite.engine/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * 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 @@ -9,7 +9,7 @@ */ /** - * Provides a {@linkplain org.junit.platform.engine.TestEngine} for running + * Provides a {@link org.junit.platform.engine.TestEngine} for running * declarative test suites. * * @since 1.8 diff --git a/junit-platform-suite-engine/src/nativeImage/initialize-at-build-time b/junit-platform-suite-engine/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..a6d7d06046b1 --- /dev/null +++ b/junit-platform-suite-engine/src/nativeImage/initialize-at-build-time @@ -0,0 +1,4 @@ +org.junit.platform.suite.engine.SuiteEngineDescriptor +org.junit.platform.suite.engine.SuiteLauncher +org.junit.platform.suite.engine.SuiteTestDescriptor +org.junit.platform.suite.engine.SuiteTestEngine diff --git a/junit-platform-suite-engine/src/test/README.md b/junit-platform-suite-engine/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-suite-engine/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java b/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java index 74a575d71c5e..76d5811b4b70 100644 --- a/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java +++ b/junit-platform-suite/src/module/org.junit.platform.suite/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2020 the original author or authors. + * 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 diff --git a/junit-platform-suite/src/test/README.md b/junit-platform-suite/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-suite/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java index 30fbc6ba577a..3e01ed7a9f0d 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Assertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java index 8d884d649300..0d78168096c0 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EngineExecutionResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 23884df7577b..2813765250e0 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,15 +13,18 @@ import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static org.apiguardian.api.API.Status.DEPRECATED; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase.EXECUTION; +import java.nio.file.Path; import java.util.Map; import java.util.ServiceLoader; import java.util.stream.Stream; import org.apiguardian.api.API; +import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.CollectionUtils; import org.junit.platform.commons.util.Preconditions; @@ -34,6 +37,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.EngineDiscoveryOrchestrator; import org.junit.platform.launcher.core.EngineExecutionOrchestrator; @@ -249,8 +253,8 @@ private static void executeDirectly(TestEngine testEngine, EngineDiscoveryReques EngineExecutionListener listener) { UniqueId engineUniqueId = UniqueId.forEngine(testEngine.getId()); TestDescriptor engineTestDescriptor = testEngine.discover(discoveryRequest, engineUniqueId); - ExecutionRequest request = new ExecutionRequest(engineTestDescriptor, listener, - discoveryRequest.getConfigurationParameters()); + ExecutionRequest request = ExecutionRequest.create(engineTestDescriptor, listener, + discoveryRequest.getConfigurationParameters(), discoveryRequest.getOutputDirectoryProvider()); testEngine.execute(request); } @@ -295,7 +299,8 @@ private EngineTestKit() { public static final class Builder { private final LauncherDiscoveryRequestBuilder requestBuilder = LauncherDiscoveryRequestBuilder.request() // - .enableImplicitConfigurationParameters(false); + .enableImplicitConfigurationParameters(false) // + .outputDirectoryProvider(DisabledOutputDirectoryProvider.INSTANCE); private final TestEngine testEngine; private Builder(TestEngine testEngine) { @@ -422,6 +427,25 @@ public Builder enableImplicitConfigurationParameters(boolean enabled) { return this; } + /** + * Set the {@link OutputDirectoryProvider} to use. + * + *

    If not specified, a default provider will be used that throws an + * exception when attempting to create output directories. This is done + * to avoid accidentally writing output files to the file system. + * + * @param outputDirectoryProvider the output directory provider to use; + * never {@code null} + * @return this builder for method chaining + * @since 1.12 + * @see OutputDirectoryProvider + */ + @API(status = EXPERIMENTAL, since = "1.12") + public Builder outputDirectoryProvider(OutputDirectoryProvider outputDirectoryProvider) { + this.requestBuilder.outputDirectoryProvider(outputDirectoryProvider); + return this; + } + /** * Execute tests for the configured {@link TestEngine}, * {@linkplain DiscoverySelector discovery selectors}, @@ -441,6 +465,27 @@ public EngineExecutionResults execute() { return executionRecorder.getExecutionResults(); } + private static class DisabledOutputDirectoryProvider implements OutputDirectoryProvider { + + public static final OutputDirectoryProvider INSTANCE = new DisabledOutputDirectoryProvider(); + + private static final String FAILURE_MESSAGE = "Writing outputs is disabled by default when using EngineTestKit. " + + "To enable, configure a custom OutputDirectoryProvider via EngineTestKit#outputDirectoryProvider."; + + private DisabledOutputDirectoryProvider() { + } + + @Override + public Path getRootDirectory() { + throw new JUnitException(FAILURE_MESSAGE); + } + + @Override + public Path createOutputDirectory(TestDescriptor testDescriptor) { + throw new JUnitException(FAILURE_MESSAGE); + } + + } } } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java index 190a7aaf5510..9d6187e5ccc1 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Event.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.FunctionUtils.where; @@ -22,6 +23,7 @@ import org.junit.platform.commons.util.ToStringBuilder; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -51,6 +53,23 @@ public static Event reportingEntryPublished(TestDescriptor testDescriptor, Repor return new Event(EventType.REPORTING_ENTRY_PUBLISHED, testDescriptor, entry); } + /** + * Create an {@code Event} for a published file for the supplied + * {@link TestDescriptor} and {@link FileEntry}. + * + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} + * @param file the {@code FileEntry} that was published; never {@code null} + * @return the newly created {@code Event} + * @since 1.12 + * @see EventType#FILE_ENTRY_PUBLISHED + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static Event fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + Preconditions.notNull(file, "FileEntry must not be null"); + return new Event(EventType.FILE_ENTRY_PUBLISHED, testDescriptor, file); + } + /** * Create an {@code Event} for the dynamic registration of the * supplied {@link TestDescriptor}. @@ -82,8 +101,8 @@ public static Event executionSkipped(TestDescriptor testDescriptor, String reaso * Create a started {@code Event} for the supplied * {@link TestDescriptor}. * - * @param testDescriptor the {@code TestDescriptor} associated with the - * event; never {@code null} + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} * @return the newly created {@code Event} * @see EventType#STARTED */ @@ -95,8 +114,8 @@ public static Event executionStarted(TestDescriptor testDescriptor) { * Create a finished {@code Event} for the supplied * {@link TestDescriptor} and {@link TestExecutionResult}. * - * @param testDescriptor the {@code TestDescriptor} associated with the - * event; never {@code null} + * @param testDescriptor the {@code TestDescriptor} associated with the event; + * never {@code null} * @param result the {@code TestExecutionResult} for the supplied * {@code TestDescriptor}; never {@code null} * @return the newly created {@code Event} diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java index 3431252cbef2..5f260fe3dc14 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventConditions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; import static org.assertj.core.api.Assertions.allOf; @@ -42,6 +43,7 @@ import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.TestExecutionResult.Status; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.EngineDescriptor; @@ -470,4 +472,17 @@ public static Condition reportEntry(Map keyValuePairs) { "event for report entry with key-value pairs %s", keyValuePairs); } + /** + * Create a new {@link Condition} that matches if and only if an + * {@link Event}'s {@linkplain Event#getPayload() payload} is an instance of + * {@link FileEntry} that contains a file that matches the supplied + * {@link Predicate}. + * + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public static Condition fileEntry(Predicate predicate) { + return new Condition<>(byPayload(FileEntry.class, predicate), "event for file entry with custom predicate"); + } + } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java index bd1cf3e06262..a0e38754ca61 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventStatistics.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.testkit.engine.Assertions.assertEquals; @@ -127,6 +128,20 @@ public EventStatistics reportingEntryPublished(long expected) { return this; } + /** + * Specify the number of expected file entry publication events. + * + * @param expected the expected number of events + * @return this {@code EventStatistics} for method chaining + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public EventStatistics fileEntryPublished(long expected) { + this.executables.add( + () -> assertEquals(expected, this.events.fileEntryPublished().count(), "file entry published")); + return this; + } + /** * Specify the number of expected dynamic registration events. * diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java index 09a6bdb59833..9cef63ee24f9 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/EventType.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,11 +10,13 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import org.apiguardian.api.API; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -60,6 +62,15 @@ public enum EventType { * * @see org.junit.platform.engine.EngineExecutionListener#reportingEntryPublished(TestDescriptor, ReportEntry) */ - REPORTING_ENTRY_PUBLISHED + REPORTING_ENTRY_PUBLISHED, + + /** + * Signals that a {@link TestDescriptor} published a file entry. + * + * @since 1.12 + * @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished(TestDescriptor, FileEntry) + */ + @API(status = EXPERIMENTAL, since = "1.12") + FILE_ENTRY_PUBLISHED } diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java index 256ea9f2d2d3..6866116f3ff2 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static java.util.Collections.sort; import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toList; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.testkit.engine.Event.byPayload; @@ -207,6 +208,18 @@ public Events reportingEntryPublished() { this.category + " Reporting Entry Published"); } + /** + * Get the file entry publication {@link Events} contained in this + * {@code Events} object. + * + * @return the filtered {@code Events}; never {@code null} + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + public Events fileEntryPublished() { + return new Events(eventsByType(EventType.FILE_ENTRY_PUBLISHED), this.category + " File Entry Published"); + } + /** * Get the dynamic registration {@link Events} contained in this * {@code Events} object. diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java index 9b5a70846d83..47f592114b2c 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Execution.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java index c7f42f5693af..d27b058160da 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/ExecutionRecorder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.platform.testkit.engine; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.MAINTAINED; import java.util.List; @@ -19,6 +20,7 @@ import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; /** @@ -82,6 +84,17 @@ public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry e this.events.add(Event.reportingEntryPublished(testDescriptor, entry)); } + /** + * Record an {@link Event} for a published {@link FileEntry}. + * + * @since 1.12 + */ + @API(status = EXPERIMENTAL, since = "1.12") + @Override + public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + this.events.add(Event.fileEntryPublished(testDescriptor, file)); + } + /** * Get the state of the engine's execution in the form of {@link EngineExecutionResults}. * diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java index bbf1d2df1a3f..fae09e861905 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Executions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java index 99035a3bdc16..092c10fe1440 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TerminationInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java index 1ebbeabb875c..f9b1b1f9c53c 100644 --- a/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java +++ b/junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/TestExecutionResultConditions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java b/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java index 2a53f5ad647c..58f2033bcf62 100644 --- a/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java +++ b/junit-platform-testkit/src/module/org.junit.platform.testkit/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-platform-testkit/src/test/README.md b/junit-platform-testkit/src/test/README.md new file mode 100644 index 000000000000..6e2fd0b363f0 --- /dev/null +++ b/junit-platform-testkit/src/test/README.md @@ -0,0 +1 @@ +For compatibility with the Eclipse IDE, the test for this module are in the `platform-tests` project. diff --git a/junit-vintage-engine/junit-vintage-engine.gradle.kts b/junit-vintage-engine/junit-vintage-engine.gradle.kts index 950aed698a14..3d1231829fac 100644 --- a/junit-vintage-engine/junit-vintage-engine.gradle.kts +++ b/junit-vintage-engine/junit-vintage-engine.gradle.kts @@ -1,6 +1,7 @@ plugins { id("junitbuild.java-library-conventions") id("junitbuild.junit4-compatibility") + id("junitbuild.native-image-properties") id("junitbuild.testing-conventions") `java-test-fixtures` groovy @@ -23,6 +24,8 @@ dependencies { testImplementation(projects.junitPlatformSuiteEngine) testImplementation(projects.junitPlatformTestkit) testImplementation(testFixtures(projects.junitJupiterApi)) + testImplementation(testFixtures(projects.junitPlatformLauncher)) + testImplementation(testFixtures(projects.junitPlatformReporting)) osgiVerification(projects.junitPlatformLauncher) } @@ -88,3 +91,10 @@ tasks { dependsOn(testWithoutJUnit4) } } + +eclipse { + classpath { + // Avoid exposing test resources to dependent projects + containsTestFixtures = false + } +} diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java new file mode 100644 index 000000000000..abb85e3eb61d --- /dev/null +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/Constants.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine; + +import static org.apiguardian.api.API.Status.EXPERIMENTAL; +import static org.apiguardian.api.API.Status.STABLE; + +import org.apiguardian.api.API; + +/** + * Collection of constants related to the {@link VintageTestEngine}. + * + * @since 5.12 + */ +@API(status = STABLE, since = "5.12") +public final class Constants { + + /** + * Indicates whether parallel execution is enabled for the JUnit Vintage engine. + * + *

    Set this property to {@code true} to enable parallel execution of tests. + * Defaults to {@code false}. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static final String PARALLEL_EXECUTION_ENABLED = "junit.vintage.execution.parallel.enabled"; + + /** + * Specifies the size of the thread pool to be used for parallel execution. + * + *

    Set this property to an integer value to specify the number of threads + * to be used for parallel execution. Defaults to the number of available + * processors. + * + * @since 5.12 + */ + @API(status = EXPERIMENTAL, since = "5.12") + public static final String PARALLEL_POOL_SIZE = "junit.vintage.execution.parallel.pool-size"; + + private Constants() { + /* no-op */ + } + +} diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java index bcc7d128b935..f27eaef647ca 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/JUnit4VersionCheck.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java index be13d89580a7..3d6840592891 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/VintageTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,12 +12,24 @@ import static org.apiguardian.api.API.Status.INTERNAL; import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.vintage.engine.Constants.PARALLEL_EXECUTION_ENABLED; +import static org.junit.vintage.engine.Constants.PARALLEL_POOL_SIZE; import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.ENGINE_ID; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.apiguardian.api.API; +import org.junit.platform.commons.logging.Logger; +import org.junit.platform.commons.logging.LoggerFactory; +import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; @@ -37,6 +49,11 @@ @API(status = INTERNAL, since = "4.12") public final class VintageTestEngine implements TestEngine { + private static final Logger logger = LoggerFactory.getLogger(VintageTestEngine.class); + + private static final int DEFAULT_THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors(); + private static final int SHUTDOWN_TIMEOUT_SECONDS = 30; + @Override public String getId() { return ENGINE_ID; @@ -69,11 +86,73 @@ public void execute(ExecutionRequest request) { EngineExecutionListener engineExecutionListener = request.getEngineExecutionListener(); VintageEngineDescriptor engineDescriptor = (VintageEngineDescriptor) request.getRootTestDescriptor(); engineExecutionListener.executionStarted(engineDescriptor); - executeAllChildren(engineDescriptor, engineExecutionListener); + executeAllChildren(engineDescriptor, engineExecutionListener, request); engineExecutionListener.executionFinished(engineDescriptor, successful()); } private void executeAllChildren(VintageEngineDescriptor engineDescriptor, + EngineExecutionListener engineExecutionListener, ExecutionRequest request) { + boolean parallelExecutionEnabled = getParallelExecutionEnabled(request); + + if (parallelExecutionEnabled) { + if (executeInParallel(engineDescriptor, engineExecutionListener, request)) { + Thread.currentThread().interrupt(); + } + } + else { + executeSequentially(engineDescriptor, engineExecutionListener); + } + } + + private boolean executeInParallel(VintageEngineDescriptor engineDescriptor, + EngineExecutionListener engineExecutionListener, ExecutionRequest request) { + ExecutorService executorService = Executors.newFixedThreadPool(getThreadPoolSize(request)); + RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); + + List> futures = new ArrayList<>(); + for (Iterator iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) { + TestDescriptor descriptor = iterator.next(); + CompletableFuture future = CompletableFuture.runAsync(() -> { + runnerExecutor.execute((RunnerTestDescriptor) descriptor); + }, executorService); + + futures.add(future); + iterator.remove(); + } + + CompletableFuture allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + boolean wasInterrupted = false; + try { + allOf.get(); + } + catch (InterruptedException e) { + logger.warn(e, () -> "Interruption while waiting for parallel test execution to finish"); + wasInterrupted = true; + } + catch (ExecutionException e) { + throw ExceptionUtils.throwAsUncheckedException(e.getCause()); + } + finally { + shutdownExecutorService(executorService); + } + return wasInterrupted; + } + + private void shutdownExecutorService(ExecutorService executorService) { + try { + executorService.shutdown(); + if (!executorService.awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + logger.warn(() -> "Executor service did not terminate within the specified timeout"); + executorService.shutdownNow(); + } + } + catch (InterruptedException e) { + logger.warn(e, () -> "Interruption while waiting for executor service to shut down"); + Thread.currentThread().interrupt(); + } + } + + private void executeSequentially(VintageEngineDescriptor engineDescriptor, EngineExecutionListener engineExecutionListener) { RunnerExecutor runnerExecutor = new RunnerExecutor(engineExecutionListener); for (Iterator iterator = engineDescriptor.getModifiableChildren().iterator(); iterator.hasNext();) { @@ -82,4 +161,21 @@ private void executeAllChildren(VintageEngineDescriptor engineDescriptor, } } + private boolean getParallelExecutionEnabled(ExecutionRequest request) { + return request.getConfigurationParameters().getBoolean(PARALLEL_EXECUTION_ENABLED).orElse(false); + } + + private int getThreadPoolSize(ExecutionRequest request) { + Optional poolSize = request.getConfigurationParameters().get(PARALLEL_POOL_SIZE); + if (poolSize.isPresent()) { + try { + return Integer.parseInt(poolSize.get()); + } + catch (NumberFormatException e) { + logger.warn(() -> "Invalid value for parallel pool size: " + poolSize.get()); + } + } + return DEFAULT_THREAD_POOL_SIZE; + } + } diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java index 85e9c0b51a5d..a55ffd3c572f 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/DescriptionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java index 4826c67faa78..3d8e4bc72714 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/OrFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java index 01f890fd50ae..93db77c7a4af 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java index 414ddc2b4955..e6c8fdb4dd72 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java index bd20da74a3d2..8d12fa032a8d 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java index 3797f1014209..2037f7aeb8d8 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/TestSourceProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,8 +14,9 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; +import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.util.FunctionUtils.where; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import java.lang.reflect.Method; import java.util.List; @@ -72,7 +73,8 @@ private String sanitizeMethodName(String methodName) { } private Method findMethod(Class testClass, String methodName) { - List methods = methodsCache.computeIfAbsent(testClass, clazz -> findMethods(clazz, m -> true)).stream() // + List methods = methodsCache.computeIfAbsent(testClass, + clazz -> findMethods(clazz, m -> true, TOP_DOWN)).stream() // .filter(where(Method::getName, isEqual(methodName))) // .collect(toList()); if (methods.isEmpty()) { diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java index 26356fdfe9f7..0e966081652c 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageEngineDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java index 89435681207c..47319da6e3b6 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/VintageTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java index 6db805e7968b..e8a005591a40 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/ClassSelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,8 +17,8 @@ import java.util.Optional; import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId.Segment; @@ -43,7 +43,10 @@ class ClassSelectorResolver implements SelectorResolver { @Override public Resolution resolve(ClassSelector selector, Context context) { - return resolveTestClass(selector.getJavaClass(), context); + if (classFilter.match(selector.getClassName())) { + return resolveTestClassThatPassedNameFilter(selector.getJavaClass(), context); + } + return unresolved(); } @Override @@ -51,15 +54,17 @@ public Resolution resolve(UniqueIdSelector selector, Context context) { Segment lastSegment = selector.getUniqueId().getLastSegment(); if (SEGMENT_TYPE_RUNNER.equals(lastSegment.getType())) { String testClassName = lastSegment.getValue(); - Class testClass = ReflectionUtils.tryToLoadClass(testClassName)// - .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); - return resolveTestClass(testClass, context); + if (classFilter.match(testClassName)) { + Class testClass = ReflectionSupport.tryToLoadClass(testClassName)// + .getOrThrow(cause -> new JUnitException("Unknown class: " + testClassName, cause)); + return resolveTestClassThatPassedNameFilter(testClass, context); + } } return unresolved(); } - private Resolution resolveTestClass(Class testClass, Context context) { - if (!classFilter.test(testClass)) { + private Resolution resolveTestClassThatPassedNameFilter(Class testClass, Context context) { + if (!classFilter.match(testClass)) { return unresolved(); } Runner runner = RUNNER_BUILDER.safeRunnerForClass(testClass); diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java index 5d03e0a64133..db80b987d897 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java index 636c9a28ee27..ba7c1b75025a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/FilterableIgnoringRunnerDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java index d95c1d68e57b..93b621ceb9b7 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IgnoringRunnerDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java index 0d1f04831598..91e331540f46 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,9 +11,9 @@ package org.junit.vintage.engine.discovery; import static org.apiguardian.api.API.Status.INTERNAL; -import static org.junit.platform.commons.util.ReflectionUtils.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isAbstract; +import static org.junit.platform.commons.support.ModifierSupport.isPublic; import static org.junit.platform.commons.util.ReflectionUtils.isInnerClass; -import static org.junit.platform.commons.util.ReflectionUtils.isPublic; import java.util.function.Predicate; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java index 75352330d1c2..3ef5419adccd 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java index f3227d857526..6511fe8c5af8 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/MethodSelectorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java index e124236e1ac2..8c98ea114e84 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java index 400d64b52f0b..a6d15af76b80 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/UniqueIdFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java index a8868df3abd4..5f50c2c439f0 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/VintageDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,7 @@ import static org.apiguardian.api.API.Status.INTERNAL; import org.apiguardian.api.API; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java index 75177f5527b4..0004ce56efe4 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/EventType.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java index 63d415bd1c11..ae0aab651df9 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java index cb0a1d5c35ee..7e5bf6b1306a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunnerExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java index bf0b160b3a83..0e1a4e0ec2e7 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/TestRun.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java index 7e396d74bcaf..d9cae90a119a 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java index c0fc50aadb2c..6986433ff5e3 100644 --- a/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java +++ b/junit-vintage-engine/src/main/java/org/junit/vintage/engine/support/UniqueIdStringifier.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java b/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java index 859744fcec17..6880a39df91d 100644 --- a/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java +++ b/junit-vintage-engine/src/module/org.junit.vintage.engine/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 @@ -9,12 +9,12 @@ */ /** - * Provides a {@linkplain org.junit.platform.engine.TestEngine} for running - * JUnit 3 and 4 based tests on the platform. + * Provides a {@link org.junit.platform.engine.TestEngine} for running JUnit 3 + * and 4 based tests on the platform. * * @since 4.12 * @provides org.junit.platform.engine.TestEngine The {@code VintageTestEngine} - * runs JUnit 3 and 4 based tests on the platform. + * runs JUnit 3 and 4 based tests on the platform. */ module org.junit.vintage.engine { requires junit; // 4 diff --git a/junit-vintage-engine/src/nativeImage/initialize-at-build-time b/junit-vintage-engine/src/nativeImage/initialize-at-build-time new file mode 100644 index 000000000000..75ff3d41de5a --- /dev/null +++ b/junit-vintage-engine/src/nativeImage/initialize-at-build-time @@ -0,0 +1,5 @@ +org.junit.vintage.engine.VintageTestEngine +org.junit.vintage.engine.descriptor.RunnerTestDescriptor +org.junit.vintage.engine.descriptor.VintageEngineDescriptor +org.junit.vintage.engine.support.UniqueIdReader +org.junit.vintage.engine.support.UniqueIdStringifier diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java index 7ef1faa72686..5ac12b0a1f26 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4ParameterizedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java index af9002c615c6..fc3416f404af 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/JUnit4VersionCheckTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java index 40b0db53a478..975752edee61 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageLauncherIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java index c51b0875fd88..3f6fa7b5e5d7 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineBasicTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java index b59d68144595..7425889bccb7 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineDiscoveryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java index dee38a4fec62..4d47f228f241 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; @@ -46,7 +47,6 @@ import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; -import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -397,14 +397,6 @@ public void executionSkipped(TestDescriptor testDescriptor, String reason) { PlainJUnit4TestCaseWithLifecycleMethods.EVENTS.add( "executionSkipped:" + testDescriptor.getDisplayName()); } - - @Override - public void dynamicTestRegistered(TestDescriptor testDescriptor) { - } - - @Override - public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { - } }; execute(testClass, listener); @@ -932,8 +924,8 @@ private static void execute(Class testClass, EngineExecutionListener listener TestEngine testEngine = new VintageTestEngine(); var discoveryRequest = request(testClass); var engineTestDescriptor = testEngine.discover(discoveryRequest, UniqueId.forEngine(testEngine.getId())); - testEngine.execute( - new ExecutionRequest(engineTestDescriptor, listener, discoveryRequest.getConfigurationParameters())); + testEngine.execute(ExecutionRequest.create(engineTestDescriptor, listener, + discoveryRequest.getConfigurationParameters(), dummyOutputDirectoryProvider())); } private static LauncherDiscoveryRequest request(Class testClass) { diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java index 1de375649de0..d45ca3b2139b 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java index 92a9e1bc0544..5386de3762bf 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageUniqueIdBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java index 4401da092743..5da33f4db28b 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/DescriptionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,8 +20,7 @@ import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import org.junit.platform.commons.util.ClassFilter; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.runner.Description; import org.junit.vintage.engine.discovery.IsPotentialJUnit4TestClass; @@ -32,8 +31,8 @@ class DescriptionUtilsTests { @TestFactory Stream computedMethodNameCorrectly() { - var classFilter = ClassFilter.of(new IsPotentialJUnit4TestClass()); - var testClasses = ReflectionUtils.findAllClassesInPackage("org.junit.vintage.engine.samples", classFilter); + var testClasses = ReflectionSupport.findAllClassesInPackage("org.junit.vintage.engine.samples", + new IsPotentialJUnit4TestClass(), name -> true); return testClasses.stream().flatMap(this::toDynamicTests); } diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java index 4cc9c5c856fe..6e5d29569b23 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/OrFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java index b182da828175..fc0004d408d5 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/TestSourceProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java index 349cdc8fb409..cb1d22b10d5f 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/descriptor/VintageTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java index f19516a527c6..85486d998c89 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/IsPotentialJUnit4TestClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java index 01733137bab1..ef8d9586697e 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/RunnerTestDescriptorPostProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java index 806e69769556..495083eb494e 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/discovery/VintageDiscovererTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -125,6 +125,7 @@ private TestDescriptor discover(EngineDiscoveryRequest request) { return new VintageDiscoverer().discover(request, engineId()); } + @SuppressWarnings("JUnitMalformedDeclaration") public static class Foo { @org.junit.Test @@ -133,6 +134,7 @@ public void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") public static class Bar { @org.junit.Test diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/ParallelExecutionIntegrationTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/ParallelExecutionIntegrationTests.java new file mode 100644 index 000000000000..9f535007ab77 --- /dev/null +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/ParallelExecutionIntegrationTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.execution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.started; +import static org.junit.vintage.engine.Constants.PARALLEL_EXECUTION_ENABLED; +import static org.junit.vintage.engine.Constants.PARALLEL_POOL_SIZE; +import static org.junit.vintage.engine.descriptor.VintageTestDescriptor.SEGMENT_TYPE_RUNNER; +import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelTestCase.AbstractBlockingTestCase; +import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelTestCase.FirstTestCase; +import static org.junit.vintage.engine.samples.junit4.JUnit4ParallelTestCase.ThirdTestCase; + +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Event; +import org.junit.platform.testkit.engine.Events; +import org.junit.vintage.engine.VintageTestEngine; +import org.junit.vintage.engine.samples.junit4.JUnit4ParallelTestCase.SecondTestCase; + +class ParallelExecutionIntegrationTests { + + @Test + void executesTestClassesInParallel(TestReporter reporter) { + AbstractBlockingTestCase.threadNames.clear(); + AbstractBlockingTestCase.countDownLatch = new CountDownLatch(3); + + var events = executeInParallelSuccessfully(3, FirstTestCase.class, SecondTestCase.class, + ThirdTestCase.class).list(); + + var startedTimestamps = getTimestampsFor(events, event(container(SEGMENT_TYPE_RUNNER), started())); + var finishedTimestamps = getTimestampsFor(events, + event(container(SEGMENT_TYPE_RUNNER), finishedSuccessfully())); + var threadNames = new HashSet<>(AbstractBlockingTestCase.threadNames); + + reporter.publishEntry("startedTimestamps", startedTimestamps.toString()); + reporter.publishEntry("finishedTimestamps", finishedTimestamps.toString()); + + assertThat(startedTimestamps).hasSize(3); + assertThat(finishedTimestamps).hasSize(3); + assertThat(startedTimestamps).allMatch(startTimestamp -> finishedTimestamps.stream().noneMatch( + finishedTimestamp -> finishedTimestamp.isBefore(startTimestamp))); + assertThat(threadNames).hasSize(3); + } + + private List getTimestampsFor(List events, Condition condition) { + // @formatter:off + return events.stream() + .filter(condition::matches) + .map(Event::getTimestamp) + .toList(); + // @formatter:on + } + + private Events executeInParallelSuccessfully(int poolSize, Class... testClasses) { + var events = execute(poolSize, testClasses).allEvents(); + try { + return events.assertStatistics(it -> it.failed(0)); + } + catch (AssertionError error) { + events.debug(); + throw error; + } + } + + private static EngineExecutionResults execute(int poolSize, Class... testClass) { + return EngineTestKit.execute(new VintageTestEngine(), request(poolSize, testClass)); + } + + private static LauncherDiscoveryRequest request(int poolSize, Class... testClasses) { + var classSelectors = Arrays.stream(testClasses) // + .map(DiscoverySelectors::selectClass) // + .toArray(ClassSelector[]::new); + + return LauncherDiscoveryRequestBuilder.request() // + .selectors(classSelectors) // + .configurationParameter(PARALLEL_EXECUTION_ENABLED, String.valueOf(true)) // + .configurationParameter(PARALLEL_POOL_SIZE, String.valueOf(poolSize)) // + .build(); + } + +} diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java index cea6d3c6ee86..6d80e7f2938b 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java index b2eff398baa9..1da96bf1401f 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java index 3d8cc0af798c..d8859b1e1c31 100644 --- a/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java +++ b/junit-vintage-engine/src/test/java/org/junit/vintage/engine/support/UniqueIdStringifierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/test/resources/junit-platform.properties b/junit-vintage-engine/src/test/resources/junit-platform.properties new file mode 100644 index 000000000000..6efc0d5e85ce --- /dev/null +++ b/junit-vintage-engine/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true diff --git a/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy b/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy index 8abfe8d45492..17c01d26f777 100644 --- a/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy +++ b/junit-vintage-engine/src/testFixtures/groovy/org/junit/vintage/engine/samples/spock/SpockTestCaseWithUnrolledAndRegularFeatureMethods.groovy @@ -1,3 +1,12 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ package org.junit.vintage.engine.samples.spock import spock.lang.Specification diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java index fff58931677f..31f6f9c4e078 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/PlainOldJavaClassWithoutAnyTestsTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java index 46bbea61fcb4..8e59e1cbe95f 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/AbstractJUnit3TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java index e29562595ead..9f42e5dbf7f4 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/IgnoredJUnit3TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java index 5157c622dcce..91148551ed16 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3ParallelSuiteWithSubsuites.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import junit.framework.TestCase; import junit.framework.TestSuite; +@SuppressWarnings("JUnitMalformedDeclaration") public class JUnit3ParallelSuiteWithSubsuites extends TestCase { private final String arg; diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java index bf0274e2edd2..a3ed0b394af3 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSingleTestCaseWithSingleTestWhichFails.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java index 3c11ed125bbb..624938b947f6 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit3SuiteWithSubsuites.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import junit.framework.TestCase; import junit.framework.TestSuite; +@SuppressWarnings("JUnitMalformedDeclaration") public class JUnit3SuiteWithSubsuites extends TestCase { private final String arg; diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java index 288c26e92085..4a096fbb8880 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/JUnit4SuiteWithIgnoredJUnit3TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java index 932aac5156f7..aa736e306d46 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit3/PlainJUnit3TestCaseWithSingleTestWhichFails.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java index 6a88673df784..aa3b595a8764 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java index 8e5e40eb21eb..d2a2264aa811 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/AbstractJunit4TestCaseWithConstructorParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java index ba49bbe39914..62efd6e08aa5 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/Categories.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java index e435cd0e682f..3c85894ecbf9 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/CompletelyDynamicTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java index a717a96cf578..6be6b3f214ce 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConcreteJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java index c10e61b28a94..51ef703b2d70 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ConfigurableRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java index 6ebfb7e46625..b18a20202391 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/DynamicRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java index ade1340dae5c..c0da6ece4f98 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EmptyIgnoredTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java index b315b15b798f..35f92e388961 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java index 3305da289100..c4e24e94ca41 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/EnclosedWithParameterizedChildrenJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java index 00ee38f1eb7f..dd5a946fc75c 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ExceptionThrowingRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java index 9a9f860cfe07..0a2dfddb7127 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java index 4a491c253c24..a3237ed8fcef 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredJUnit4TestCaseWithNotFilterableRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java index d2625c1e5bc3..582846c551e4 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/IgnoredParameterizedTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParallelTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParallelTestCase.java new file mode 100644 index 000000000000..4e8e0c62c8c2 --- /dev/null +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParallelTestCase.java @@ -0,0 +1,67 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.vintage.engine.samples.junit4; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.junit.runner.RunWith; + +@RunWith(Enclosed.class) +public class JUnit4ParallelTestCase { + + public static class AbstractBlockingTestCase { + + public static final Set threadNames = ConcurrentHashMap.newKeySet(); + public static CountDownLatch countDownLatch; + + @Rule + public final TestWatcher testWatcher = new TestWatcher() { + @Override + protected void starting(Description description) { + AbstractBlockingTestCase.threadNames.add(Thread.currentThread().getName()); + } + }; + + @Test + public void test() throws Exception { + countDownAndBlock(countDownLatch); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static void countDownAndBlock(CountDownLatch countDownLatch) throws InterruptedException { + countDownLatch.countDown(); + countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); + } + + private static long estimateSimulatedTestDurationInMilliseconds() { + var runningInCi = Boolean.parseBoolean(System.getenv("CI")); + return runningInCi ? 1000 : 100; + } + } + + public static class FirstTestCase extends AbstractBlockingTestCase { + } + + public static class SecondTestCase extends AbstractBlockingTestCase { + } + + public static class ThirdTestCase extends AbstractBlockingTestCase { + } +} diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java index fe05d745fc61..cfa90bb45292 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4ParameterizedTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java index 9955d8e10806..12ee418ef5e8 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithFilterableChildRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java index dbfd835d67ee..714494651a89 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithIgnoredJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java index d81e679e8ee7..cc797b0bfa58 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java index cab393ed7b4c..cf804e743535 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteOfSuiteWithJUnit4TestCaseWithErrorInBeforeClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java index f6fc59535ee8..08749c9f5ca3 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithExceptionThrowingRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java index c0b7351e9c3c..2c4dc7ee5205 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithIgnoredJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java index 8d055eb75720..1d12b78a9363 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit3SuiteWithSingleTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java index 2bffda246870..5057bd76c64e 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithAssumptionFailureInBeforeClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java index 2d2a0eff61a9..962b42c33979 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithErrorInBeforeClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java index 3477bc366a72..e25ef33f4bdf 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java index 6f526104e679..b2fd029e5d6b 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithJUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java index 3b8fd42c965a..6ffd686b1f6e 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithPlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java index 498b80d20d5b..c446a1710fdb 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4SuiteWithTwoTestCases.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java index f628dbd5f5ed..f8c86095d98e 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithAssumptionFailureInBeforeClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java index 553ab1031f7b..98182465ca7a 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithDistinguishableOverloadedMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java index b47efc989451..cbe1fcede463 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorCollectorStoringMultipleFailures.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java index 00903b8b1999..72d8ff1f5e64 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInAfterClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java index 53bd47ed80d9..bec3a0dd2bff 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithErrorInBeforeClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java index 3fdde38963bc..7e356c7ab18a 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithExceptionThrowingRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java index f81696ad4fe0..03d49298bc7f 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithFailingDescriptionThatIsNotReportedAsFinished.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java index e590208ccf11..888437d18ac8 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithIndistinguishableOverloadedMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java index a36112ba5c26..90d5064448db 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithNotFilterableRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java index 1fccc00f7819..07dab7575284 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithCustomUniqueIdsAndDisplayNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java index 9b82ffc916c0..f772b638133d 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/JUnit4TestCaseWithRunnerWithDuplicateChangingChildDescriptions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java index d3c318003317..711dc90adb16 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/MalformedJUnit4TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java index 5fcdfca51ef8..3371d99c281b 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/NotFilterableRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java index eae9c787c323..15f37423b6d0 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java index 9330453cd681..bcf6af174f06 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedTimingTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java index 73c6219695eb..46c56263b4bb 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithAfterParamFailureTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java index 685e231dff94..1c960485b091 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/ParameterizedWithBeforeParamFailureTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java index fb23515ea145..3f63378233d7 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithFiveTestMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java index 12c24a541271..8668768bead3 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithLifecycleMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java index 2d7805ec5d71..35d9760b9f78 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleInheritedTestWhichFails.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java index c9b165c261b6..bd7f5125a9db 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichFails.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java index 697a8ac1842d..9186161116a5 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithSingleTestWhichIsIgnored.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java index 8055a7cee501..ea9b067ab902 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/PlainJUnit4TestCaseWithTwoTestMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java index 29d379200443..99a713b9eff0 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerThatOnlyReportsFailures.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java index bdf04f25ff2f..f3af1814485d 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/RunnerWithCustomUniqueIdsAndDisplayNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java index 37f7671e7169..5e8f1e3d222c 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/SingleFailingTheoryTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java index 2a724e128f12..fe1011fac50e 100644 --- a/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java +++ b/junit-vintage-engine/src/testFixtures/java/org/junit/vintage/engine/samples/junit4/TestCaseRunWithJUnitPlatformRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/jupiter-tests/jupiter-tests.gradle.kts b/jupiter-tests/jupiter-tests.gradle.kts new file mode 100644 index 000000000000..061920f4d96d --- /dev/null +++ b/jupiter-tests/jupiter-tests.gradle.kts @@ -0,0 +1,40 @@ +import org.gradle.api.tasks.PathSensitivity.RELATIVE + +plugins { + id("junitbuild.code-generator") + id("junitbuild.kotlin-library-conventions") + id("junitbuild.junit4-compatibility") + id("junitbuild.testing-conventions") + groovy +} + +dependencies { + testImplementation(projects.junitJupiter) + testImplementation(projects.junitJupiterMigrationsupport) + testImplementation(projects.junitPlatformLauncher) + testImplementation(projects.junitPlatformSuiteEngine) + testImplementation(projects.junitPlatformTestkit) + testImplementation(testFixtures(projects.junitPlatformCommons)) + testImplementation(kotlin("stdlib")) + testImplementation(libs.jimfs) + testImplementation(libs.junit4) + testImplementation(libs.kotlinx.coroutines) + testImplementation(libs.groovy4) + testImplementation(libs.memoryfilesystem) + testImplementation(testFixtures(projects.junitJupiterApi)) + testImplementation(testFixtures(projects.junitJupiterEngine)) + testImplementation(testFixtures(projects.junitPlatformLauncher)) + testImplementation(testFixtures(projects.junitPlatformReporting)) +} + +tasks { + test { + inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) + systemProperty("developmentVersion", version) + } + test_4_12 { + filter { + includeTestsMatching("org.junit.jupiter.migrationsupport.*") + } + } +} diff --git a/junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte similarity index 55% rename from junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte rename to jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte index fc4d8884be75..b6ce05e15fa6 100644 --- a/junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte +++ b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreConditionTests.java.jte @@ -7,8 +7,7 @@ ${licenseHeader} package org.junit.jupiter.api.condition; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @for(var jre : jresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- @@ -19,7 +18,8 @@ import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.PreconditionViolationException; /** - * Unit tests for {@link DisabledOnJreCondition}. + * Unit tests for {@link DisabledOnJreCondition}, generated from + * {@code DisabledOnJreConditionTests.java.jte}. * *

    Note that test method names MUST match the test method names in * {@link DisabledOnJreIntegrationTests}. @@ -28,6 +28,8 @@ import org.junit.platform.commons.PreconditionViolationException; */ class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { + private static final String JAVA_VERSION = System.getProperty("java.version"); + @Override protected ExecutionCondition getExecutionCondition() { return new DisabledOnJreCondition(); @@ -49,12 +51,33 @@ class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { } /** - * @see DisabledOnJreIntegrationTests#missingJreDeclaration() + * @see DisabledOnJreIntegrationTests#missingVersionDeclaration() + */ + @Test + void missingVersionDeclaration() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("You must declare at least one JRE or version in @DisabledOnJre"); + } + + /** + * @see DisabledOnJreIntegrationTests#jreUndefined() + */ + @Test + void jreUndefined() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("JRE.UNDEFINED is not supported in @DisabledOnJre"); + } + + /** + * @see DisabledOnJreIntegrationTests#version7() */ @Test - void missingJreDeclaration() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("You must declare at least one JRE"); + void version7() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("Version [7] in @DisabledOnJre must be greater than or equal to 8"); } /** @@ -68,10 +91,20 @@ class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { } @for(var jre : jres) /** - * @see DisabledOnJreIntegrationTests#java${jre.getVersion()}() + * @see DisabledOnJreIntegrationTests#jre${jre.getVersion()}() + */ + @Test + void jre${jre.getVersion()}() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava${jre.getVersion()}()); + } +@endfor<%-- +--%>@for(var jre : jres) + /** + * @see DisabledOnJreIntegrationTests#version${jre.getVersion()}() */ @Test - void java${jre.getVersion()}() { + void version${jre.getVersion()}() { evaluateCondition(); assertDisabledOnCurrentJreIf(onJava${jre.getVersion()}()); } @@ -88,11 +121,11 @@ class DisabledOnJreConditionTests extends AbstractExecutionConditionTests { private void assertDisabledOnCurrentJreIf(boolean condition) { if (condition) { assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); + assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); } else { assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); + assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); } } diff --git a/junit-jupiter-engine/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 similarity index 70% rename from junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte rename to jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/DisabledOnJreIntegrationTests.java.jte index 6d86cd5c670b..c84b159c4078 100644 --- a/junit-jupiter-engine/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 @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.fail; --%>import static org.junit.jupiter.api.condition.JRE.JAVA_${jre.getVersion()}; @endfor<%-- --%>import static org.junit.jupiter.api.condition.JRE.OTHER; +import static org.junit.jupiter.api.condition.JRE.UNDEFINED; @for(var jre : jresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- @@ -23,7 +24,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** - * Integration tests for {@link DisabledOnJre}. + * Integration tests for {@link DisabledOnJre @DisabledOnJre}, generated from + * {@code DisabledOnJreIntegrationTests.java.jte}. * * @since 5.1 */ @@ -37,7 +39,19 @@ class DisabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") @DisabledOnJre({}) - void missingJreDeclaration() { + void missingVersionDeclaration() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledOnJre(UNDEFINED) + void jreUndefined() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledOnJre(versions = 7) + void version7() { } @Test @@ -53,7 +67,14 @@ class DisabledOnJreIntegrationTests { @for(var jre : jres) @Test @DisabledOnJre(JAVA_${jre.getVersion()}) - void java${jre.getVersion()}() { + void jre${jre.getVersion()}() { + assertFalse(onJava${jre.getVersion()}()); + } +@endfor<%-- +--%>@for(var jre : jres) + @Test + @DisabledOnJre(versions = ${jre.getVersion()}) + void version${jre.getVersion()}() { assertFalse(onJava${jre.getVersion()}()); } @endfor diff --git a/junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte similarity index 55% rename from junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte rename to jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte index 81c9e1cc4dfd..9d6a1ee02021 100644 --- a/junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte +++ b/jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreConditionTests.java.jte @@ -7,8 +7,7 @@ ${licenseHeader} package org.junit.jupiter.api.condition; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @for(var jre : jresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- @@ -19,7 +18,8 @@ import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.platform.commons.PreconditionViolationException; /** - * Unit tests for {@link EnabledOnJreCondition}. + * Unit tests for {@link EnabledOnJreCondition}, generated from + * {@code EnabledOnJreConditionTests.java.jte}. * *

    Note that test method names MUST match the test method names in * {@link EnabledOnJreIntegrationTests}. @@ -28,6 +28,8 @@ import org.junit.platform.commons.PreconditionViolationException; */ class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { + private static final String JAVA_VERSION = System.getProperty("java.version"); + @Override protected ExecutionCondition getExecutionCondition() { return new EnabledOnJreCondition(); @@ -49,12 +51,33 @@ class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { } /** - * @see EnabledOnJreIntegrationTests#missingJreDeclaration() + * @see EnabledOnJreIntegrationTests#missingVersionDeclaration() + */ + @Test + void missingVersionDeclaration() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("You must declare at least one JRE or version in @EnabledOnJre"); + } + + /** + * @see EnabledOnJreIntegrationTests#jreUndefined() + */ + @Test + void jreUndefined() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("JRE.UNDEFINED is not supported in @EnabledOnJre"); + } + + /** + * @see EnabledOnJreIntegrationTests#version7() */ @Test - void missingJreDeclaration() { - Exception exception = assertThrows(PreconditionViolationException.class, this::evaluateCondition); - assertThat(exception).hasMessageContaining("You must declare at least one JRE"); + void version7() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("Version [7] in @EnabledOnJre must be greater than or equal to 8"); } /** @@ -67,10 +90,20 @@ class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { } @for(var jre : jres) /** - * @see EnabledOnJreIntegrationTests#java${jre.getVersion()}() + * @see EnabledOnJreIntegrationTests#jre${jre.getVersion()}() + */ + @Test + void jre${jre.getVersion()}() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava${jre.getVersion()}()); + } +@endfor<%-- +--%>@for(var jre : jres) + /** + * @see EnabledOnJreIntegrationTests#version${jre.getVersion()}() */ @Test - void java${jre.getVersion()}() { + void version${jre.getVersion()}() { evaluateCondition(); assertEnabledOnCurrentJreIf(onJava${jre.getVersion()}()); } @@ -88,11 +121,11 @@ class EnabledOnJreConditionTests extends AbstractExecutionConditionTests { private void assertEnabledOnCurrentJreIf(boolean condition) { if (condition) { assertEnabled(); - assertReasonContains("Enabled on JRE version: " + System.getProperty("java.version")); + assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); } else { assertDisabled(); - assertReasonContains("Disabled on JRE version: " + System.getProperty("java.version")); + assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); } } diff --git a/junit-jupiter-engine/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 similarity index 69% rename from junit-jupiter-engine/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte rename to jupiter-tests/src/templates/resources/test/org/junit/jupiter/api/condition/EnabledOnJreIntegrationTests.java.jte index 7aee1f374c73..c3b71df58ab4 100644 --- a/junit-jupiter-engine/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 @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; --%>import static org.junit.jupiter.api.condition.JRE.JAVA_${jre.getVersion()}; @endfor<%-- --%>import static org.junit.jupiter.api.condition.JRE.OTHER; +import static org.junit.jupiter.api.condition.JRE.UNDEFINED; @for(var jre : jresSortedByStringValue)<%-- --%>import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava${jre.getVersion()}; @endfor<%-- @@ -22,7 +23,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** - * Integration tests for {@link EnabledOnJre}. + * Integration tests for {@link EnabledOnJre @EnabledOnJre}, generated from + * {@code EnabledOnJreIntegrationTests.java.jte}. * * @since 5.1 */ @@ -36,7 +38,19 @@ class EnabledOnJreIntegrationTests { @Test @Disabled("Only used in a unit test via reflection") @EnabledOnJre({}) - void missingJreDeclaration() { + void missingVersionDeclaration() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledOnJre(UNDEFINED) + void jreUndefined() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledOnJre(versions = 7) + void version7() { } @Test @@ -51,7 +65,14 @@ class EnabledOnJreIntegrationTests { @for(var jre : jres) @Test @EnabledOnJre(JAVA_${jre.getVersion()}) - void java${jre.getVersion()}() { + void jre${jre.getVersion()}() { + assertTrue(onJava${jre.getVersion()}()); + } +@endfor<%-- +--%>@for(var jre : jres) + @Test + @EnabledOnJre(versions = ${jre.getVersion()}) + void version${jre.getVersion()}() { assertTrue(onJava${jre.getVersion()}()); } @endfor diff --git a/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy b/jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy similarity index 99% rename from junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy rename to jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy index 7554f9420405..7d8ffc61d531 100644 --- a/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy +++ b/jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertEqualsTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy b/jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy similarity index 99% rename from junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy rename to jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy index bf95310e7931..06b58047f983 100644 --- a/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy +++ b/jupiter-tests/src/test/groovy/org/junit/jupiter/api/GroovyAssertNotEqualsTests.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy b/jupiter-tests/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy similarity index 96% rename from junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy rename to jupiter-tests/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy index a33d8031beef..d741fbd6bf49 100644 --- a/junit-jupiter-engine/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy +++ b/jupiter-tests/src/test/groovy/org/junit/jupiter/api/PrimitiveAndWrapperTypeHelpers.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java b/jupiter-tests/src/test/java/DefaultPackageTestCase.java similarity index 91% rename from junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java rename to jupiter-tests/src/test/java/DefaultPackageTestCase.java index 305995fcd0e2..e744df16064c 100644 --- a/junit-jupiter-engine/src/test/java/DefaultPackageTestCase.java +++ b/jupiter-tests/src/test/java/DefaultPackageTestCase.java @@ -1,6 +1,6 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/example/B_TestCase.java b/jupiter-tests/src/test/java/example/B_TestCase.java similarity index 92% rename from junit-jupiter-engine/src/test/java/example/B_TestCase.java rename to jupiter-tests/src/test/java/example/B_TestCase.java index db7cada43fea..30b66be4921f 100644 --- a/junit-jupiter-engine/src/test/java/example/B_TestCase.java +++ b/jupiter-tests/src/test/java/example/B_TestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java b/jupiter-tests/src/test/java/org/junit/jupiter/JupiterTestSuite.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java rename to jupiter-tests/src/test/java/org/junit/jupiter/JupiterTestSuite.java index e29ceeca445a..c569b5ea06f4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/JupiterTestSuite.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/JupiterTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java index 13a4c58e0f96..dac3a628fb17 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertAllAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java index 90ce5920a825..2bbfa11fe3e1 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertArrayEqualsAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java index ec290530a53e..260be584308a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertDoesNotThrowAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java index 9a41bf65882b..df84f44540a2 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertEqualsAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java index e8a24a98af17..14bef98d37ec 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertFalseAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java index 56a75ced10e0..b5bfd62438eb 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertInstanceOfAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java index bc492854d2ac..e09823ddd89d 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertIterableEqualsAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java index a76c5f6cc049..273b75d11276 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertLinesMatchAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java index 09dfe6b9b9ea..450653ecd327 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotEqualsAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java index 3a59accdd344..da2386aa0f44 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotNullAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java index 9c2ea0fd7c98..d3a2839758d9 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNotSameAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java index 773488a8c8af..e960d250a993 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertNullAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java index debc87667a39..1ee81d533990 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertSameAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java index 0ac7ef29fe97..c1f5a003cbc4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java index 7ef6e59e680f..d167f4f08e43 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertThrowsExactlyAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java index 3a3b9ae61fba..932598229940 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java index c445f86f67fc..303bbb3124fe 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTimeoutPreemptivelyAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -40,8 +40,8 @@ class AssertTimeoutPreemptivelyAssertionsTests { private static final Duration PREEMPTIVE_TIMEOUT = ofMillis(WINDOWS.isCurrentOs() ? 1000 : 100); - private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, - ____) -> new TimeoutException(); + private static final Assertions.TimeoutFailureFactory TIMEOUT_EXCEPTION_FACTORY = (__, ___, ____, + _____) -> new TimeoutException(); private static final ThreadLocal changed = ThreadLocal.withInitial(() -> new AtomicBoolean(false)); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java index 930afebda7ea..eb8e1931502b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertTrueAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java index 92ac86c4a2c3..d961acd53496 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssertionTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java index 6c9412cd0974..a63534493de9 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/AssumptionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/AssumptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java similarity index 86% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java index e35aac0ba0b5..df9ffcb35a30 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationInheritanceTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -26,6 +26,7 @@ void this_is_a_test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class StaticNestedTestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java similarity index 89% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java index fe051e37e3ee..898dfc1ea9e8 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DisplayNameGenerationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.EmptyStackException; +import java.util.List; import java.util.Stack; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; @@ -197,6 +198,33 @@ void indicativeSentencesGenerationInheritance() { ); } + @Test + void indicativeSentencesRuntimeEnclosingType() { + check(IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.class, // + "CONTAINER: Scenario 1", // + "CONTAINER: Scenario 1 -> Level 1", // + "CONTAINER: Scenario 1 -> Level 1 -> Level 2", // + "TEST: Scenario 1 -> Level 1 -> Level 2 -> this is a test"// + ); + + check(IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.class, // + "CONTAINER: Scenario 2", // + "CONTAINER: Scenario 2 -> Level 1", // + "CONTAINER: Scenario 2 -> Level 1 -> Level 2", // + "TEST: Scenario 2 -> Level 1 -> Level 2 -> this is a test"// + ); + } + + @Test + void indicativeSentencesOnSubClass() { + check(IndicativeSentencesOnSubClassScenarioOneTestCase.class, // + "CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase", // + "CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1", // + "CONTAINER: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1 -> Level 2", // + "TEST: IndicativeSentencesOnSubClassScenarioOneTestCase -> Level 1 -> Level 2 -> this is a test"// + ); + } + private void check(Class testClass, String... expectedDisplayNames) { var request = request().selectors(selectClass(testClass)).build(); var descriptors = discoverTests(request).getDescendants(); @@ -217,12 +245,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nn"; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return "nn"; } } @@ -287,6 +316,7 @@ static class UnderscoreStyleInheritedFromSuperClassTestCase extends UnderscoreSt // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("A stack") @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) static class StackTestCase { @@ -353,6 +383,7 @@ void peek_returns_that_element_without_removing_it_from_the_stack() { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("A stack") @IndicativeSentencesGeneration(generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeGeneratorTestCase { @@ -397,6 +428,7 @@ void is_no_longer_empty() { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("A stack") @IndicativeSentencesGeneration(separator = " >> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) static class IndicativeGeneratorWithCustomSeparatorTestCase { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java index 1244dc697104..073678ad4088 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/DynamicTestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java similarity index 90% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java index 43b66eeebb2e..2448a2583f4c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/EnigmaThrowable.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java index 95db2c35ccaa..d549bbbed364 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/FailAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java similarity index 87% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java index e0e779cf750b..433c14aa0723 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesGenerationInheritanceTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -26,6 +26,7 @@ void this_is_a_test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class StaticNestedTestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java index 691e8dc86647..b9b79552e3bd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesNestedTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java new file mode 100644 index 000000000000..5ad3e99cdcce --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassScenarioOneTestCase.java @@ -0,0 +1,18 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * @since 5.12 + */ +@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class) +class IndicativeSentencesOnSubClassScenarioOneTestCase extends IndicativeSentencesOnSubClassTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java new file mode 100644 index 000000000000..1127970070d1 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesOnSubClassTestCase.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * @since 5.12 + */ +@DisplayName("Base Scenario") +abstract class IndicativeSentencesOnSubClassTestCase { + + @Nested + class Level_1 { + + @Nested + class Level_2 { + + @Test + void this_is_a_test() { + } + } + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java new file mode 100644 index 000000000000..927903c14612 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * @since 5.12 + */ +@DisplayName("Scenario 1") +class IndicativeSentencesRuntimeEnclosingTypeScenarioOneTestCase + extends IndicativeSentencesRuntimeEnclosingTypeTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java new file mode 100644 index 000000000000..c6e2f377db10 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +/** + * @since 5.12 + */ +@DisplayName("Scenario 2") +class IndicativeSentencesRuntimeEnclosingTypeScenarioTwoTestCase + extends IndicativeSentencesRuntimeEnclosingTypeTestCase { +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java new file mode 100644 index 000000000000..86244f4cbfcc --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesRuntimeEnclosingTypeTestCase.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api; + +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; + +/** + * @since 5.12 + */ +@DisplayName("Base Scenario") +@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class) +abstract class IndicativeSentencesRuntimeEnclosingTypeTestCase { + + @Nested + class Level_1 { + + @Nested + class Level_2 { + + @Test + void this_is_a_test() { + } + } + } +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java index 69390065377d..b6392bfdc862 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IndicativeSentencesTopLevelTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java index 1b756c88ff46..0dcb9c312037 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/IterableFactory.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/IterableFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java index f43b586c03de..69ab15fe197b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/RandomlyOrderedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java similarity index 85% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java index 5cd1462ac566..bff8050468de 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/AbstractExecutionConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,13 +10,13 @@ package org.junit.jupiter.api.condition; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; -import static org.junit.platform.commons.util.ReflectionUtils.newInstance; +import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.support.ReflectionSupport.findMethod; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; +import static org.junit.platform.commons.support.ReflectionSupport.newInstance; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -59,13 +59,13 @@ abstract class AbstractExecutionConditionTests { void ensureAllTestMethodsAreCovered() { Predicate isTestMethod = method -> method.isAnnotationPresent(Test.class); - List methodsToTest = findMethods(getTestClass(), isTestMethod).stream()// - .map(Method::getName).sorted().collect(toList()); + List methodsToTest = findMethods(getTestClass(), isTestMethod, TOP_DOWN).stream()// + .map(Method::getName).sorted().toList(); - List localTestMethods = findMethods(getClass(), isTestMethod).stream()// - .map(Method::getName).sorted().collect(toList()); + List localTestMethods = findMethods(getClass(), isTestMethod, TOP_DOWN).stream()// + .map(Method::getName).sorted().toList(); - assertThat(localTestMethods).isEqualTo(methodsToTest); + assertThat(localTestMethods).containsExactlyElementsOf(methodsToTest); } @BeforeEach diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java new file mode 100644 index 000000000000..8703958019ed --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeConditionTests.java @@ -0,0 +1,298 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link DisabledForJreRange}. + * + *

    Note that test method names MUST match the test method names in + * {@link DisabledForJreRangeIntegrationTests}. + * + * @since 5.6 + */ +class DisabledForJreRangeConditionTests extends AbstractExecutionConditionTests { + + private static final String JAVA_VERSION = System.getProperty("java.version"); + + @Override + protected ExecutionCondition getExecutionCondition() { + return new DisabledForJreRangeCondition(); + } + + @Override + protected Class getTestClass() { + return DisabledForJreRangeIntegrationTests.class; + } + + /** + * @see DisabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@DisabledForJreRange is not present"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#defaultValues() + */ + @Test + void defaultValues() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "You must declare a non-default value for the minimum or maximum value in @DisabledForJreRange"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#effectiveJreDefaultValues() + */ + @Test + void effectiveJreDefaultValues() { + defaultValues(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#effectiveVersionDefaultValues() + */ + @Test + void effectiveVersionDefaultValues() { + defaultValues(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#min8() + */ + @Test + void min8() { + defaultValues(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersion8() + */ + @Test + void minVersion8() { + defaultValues(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#maxOther() + */ + @Test + void maxOther() { + defaultValues(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#maxVersionMaxInteger() + */ + @Test + void maxVersionMaxInteger() { + defaultValues(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersion7() + */ + @Test + void minVersion7() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("@DisabledForJreRange's minVersion [7] must be greater than or equal to 8"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#maxVersion7() + */ + @Test + void maxVersion7() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("@DisabledForJreRange's maxVersion [7] must be greater than or equal to 8"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minAndMinVersion() + */ + @Test + void minAndMinVersion() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "@DisabledForJreRange's minimum value must be configured with either a JRE enum constant or numeric version, but not both"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#maxAndMaxVersion() + */ + @Test + void maxAndMaxVersion() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "@DisabledForJreRange's maximum value must be configured with either a JRE enum constant or numeric version, but not both"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minGreaterThanMax() + */ + @Test + void minGreaterThanMax() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "@DisabledForJreRange's minimum value [21] must be less than or equal to its maximum value [11]"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minGreaterThanMaxVersion() + */ + @Test + void minGreaterThanMaxVersion() { + minGreaterThanMax(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersionGreaterThanMaxVersion() + */ + @Test + void minVersionGreaterThanMaxVersion() { + minGreaterThanMax(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersionGreaterThanMax() + */ + @Test + void minVersionGreaterThanMax() { + minGreaterThanMax(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#min18() + */ + @Test + void min18() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(!onJava17()); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersion18() + */ + @Test + void minVersion18() { + min18(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#max18() + */ + @Test + void max18() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() + || onJava14() || onJava15() || onJava16() || onJava17() || onJava18()); + } + + /** + * @see DisabledForJreRangeIntegrationTests#maxVersion18() + */ + @Test + void maxVersion18() { + max18(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#min17Max17() + */ + @Test + void min17Max17() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava17()); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersion17MaxVersion17() + */ + @Test + void minVersion17MaxVersion17() { + min17Max17(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#min18Max19() + */ + @Test + void min18Max19() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(onJava18() || onJava19()); + assertCustomDisabledReasonIs("Disabled on Java 18 & 19"); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minVersion18MaxVersion19() + */ + @Test + void minVersion18MaxVersion19() { + min18Max19(); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minOtherMaxOther() + */ + @Test + void minOtherMaxOther() { + evaluateCondition(); + assertDisabledOnCurrentJreIf(!onKnownVersion()); + } + + /** + * @see DisabledForJreRangeIntegrationTests#minMaxIntegerMaxMaxInteger() + */ + @Test + void minMaxIntegerMaxMaxInteger() { + minOtherMaxOther(); + } + + private void assertDisabledOnCurrentJreIf(boolean condition) { + if (condition) { + assertDisabled(); + assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); + } + else { + assertEnabled(); + assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); + } + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java new file mode 100644 index 000000000000..7d043196079f --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledForJreRangeIntegrationTests.java @@ -0,0 +1,218 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.JRE.JAVA_11; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; +import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import static org.junit.jupiter.api.condition.JRE.OTHER; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link DisabledForJreRange @DisabledForJreRange}. + * + * @since 5.6 + */ +class DisabledForJreRangeIntegrationTests { + + @Test + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange + void defaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(min = JAVA_8, max = OTHER) + void effectiveJreDefaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(minVersion = 8, maxVersion = Integer.MAX_VALUE) + void effectiveVersionDefaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(min = JAVA_8) + void min8() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(minVersion = 8) + void minVersion8() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(max = OTHER) + void maxOther() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(maxVersion = Integer.MAX_VALUE) + void maxVersionMaxInteger() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(minVersion = 7) + void minVersion7() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(maxVersion = 7) + void maxVersion7() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(min = JAVA_18, minVersion = 21) + void minAndMinVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(max = JAVA_18, maxVersion = 21) + void maxAndMaxVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(min = JAVA_21, max = JAVA_11) + void minGreaterThanMax() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(min = JAVA_21, maxVersion = 11) + void minGreaterThanMaxVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(minVersion = 21, maxVersion = 11) + void minVersionGreaterThanMaxVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @DisabledForJreRange(minVersion = 21, max = JAVA_11) + void minVersionGreaterThanMax() { + fail("should result in a configuration exception"); + } + + @Test + @DisabledForJreRange(min = JAVA_18) + void min18() { + assertFalse(onJava18() || onJava19()); + assertTrue(onJava17()); + } + + @Test + @DisabledForJreRange(minVersion = 18) + void minVersion18() { + min18(); + } + + @Test + @DisabledForJreRange(max = JAVA_18) + void max18() { + assertFalse(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18()); + } + + @Test + @DisabledForJreRange(maxVersion = 18) + void maxVersion18() { + max18(); + } + + @Test + @DisabledForJreRange(min = JAVA_17, max = JAVA_17) + void min17Max17() { + assertFalse(onJava17()); + } + + @Test + @DisabledForJreRange(minVersion = 17, maxVersion = 17) + void minVersion17MaxVersion17() { + min17Max17(); + } + + @Test + @DisabledForJreRange(min = JAVA_18, max = JAVA_19, disabledReason = "Disabled on Java 18 & 19") + void min18Max19() { + assertFalse(onJava18() || onJava19()); + } + + @Test + @DisabledForJreRange(minVersion = 18, maxVersion = 19, disabledReason = "Disabled on Java 18 & 19") + void minVersion18MaxVersion19() { + min18Max19(); + } + + @Test + @DisabledForJreRange(min = OTHER, max = OTHER) + void minOtherMaxOther() { + assertTrue(onKnownVersion()); + } + + @Test + @DisabledForJreRange(minVersion = Integer.MAX_VALUE, maxVersion = Integer.MAX_VALUE) + void minMaxIntegerMaxMaxInteger() { + minOtherMaxOther(); + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java similarity index 92% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java index d7ae67545a06..dcc46f577138 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionClassLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; -import org.junit.platform.commons.util.ReflectionUtils; /** * Tests for {@link DisabledIfCondition} using custom {@link ClassLoader} arrangements. @@ -40,7 +40,7 @@ void enabledWithStaticMethodInTypeFromDifferentClassLoader() throws Exception { assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); ExtensionContext context = mock(); - Method annotatedMethod = ReflectionUtils.findMethod(getClass(), "enabledMethod").get(); + Method annotatedMethod = ReflectionSupport.findMethod(getClass(), "enabledMethod").get(); when(context.getElement()).thenReturn(Optional.of(annotatedMethod)); doReturn(testClass).when(context).getRequiredTestClass(); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java index 68c91b5733d1..ff4800b4511b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java index b438f182d21d..c9c7434a5bea 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java index 19c5ca37eaa2..7cbf973286ee 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfEnvironmentVariableIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java index 8f517b6bfe10..9efc7e998edb 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java index 97a56de0e4a4..3270e6cc97da 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java index 86b85672f0ec..67f71dbaf0dd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledIfSystemPropertyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java index 06c4b316337e..8a098c8a7e04 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java index 7dff43fb7d41..5ff83a468a9a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/DisabledOnOsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java new file mode 100644 index 000000000000..886af0b8e309 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeConditionTests.java @@ -0,0 +1,354 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava20; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava21; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava22; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava23; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava24; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava25; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * Unit tests for {@link EnabledForJreRange @EnabledForJreRange}. + * + *

    Note that test method names MUST match the test method names in + * {@link EnabledForJreRangeIntegrationTests}. + * + * @since 5.6 + */ +class EnabledForJreRangeConditionTests extends AbstractExecutionConditionTests { + + private static final String JAVA_VERSION = System.getProperty("java.version"); + + @Override + protected ExecutionCondition getExecutionCondition() { + return new EnabledForJreRangeCondition(); + } + + @Override + protected Class getTestClass() { + return EnabledForJreRangeIntegrationTests.class; + } + + /** + * @see EnabledForJreRangeIntegrationTests#enabledBecauseAnnotationIsNotPresent() + */ + @Test + void enabledBecauseAnnotationIsNotPresent() { + evaluateCondition(); + assertEnabled(); + assertReasonContains("@EnabledForJreRange is not present"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#defaultValues() + */ + @Test + void defaultValues() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "You must declare a non-default value for the minimum or maximum value in @EnabledForJreRange"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#effectiveJreDefaultValues() + */ + @Test + void effectiveJreDefaultValues() { + defaultValues(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#effectiveVersionDefaultValues() + */ + @Test + void effectiveVersionDefaultValues() { + defaultValues(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min8() + */ + @Test + void min8() { + defaultValues(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion8() + */ + @Test + void minVersion8() { + defaultValues(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#maxOther() + */ + @Test + void maxOther() { + defaultValues(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#maxVersionMaxInteger() + */ + @Test + void maxVersionMaxInteger() { + defaultValues(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion7() + */ + @Test + void minVersion7() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("@EnabledForJreRange's minVersion [7] must be greater than or equal to 8"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#maxVersion7() + */ + @Test + void maxVersion7() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage("@EnabledForJreRange's maxVersion [7] must be greater than or equal to 8"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minAndMinVersion() + */ + @Test + void minAndMinVersion() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "@EnabledForJreRange's minimum value must be configured with either a JRE enum constant or numeric version, but not both"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#maxAndMaxVersion() + */ + @Test + void maxAndMaxVersion() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "@EnabledForJreRange's maximum value must be configured with either a JRE enum constant or numeric version, but not both"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minGreaterThanMax() + */ + @Test + void minGreaterThanMax() { + assertThatExceptionOfType(PreconditionViolationException.class)// + .isThrownBy(this::evaluateCondition)// + .withMessage( + "@EnabledForJreRange's minimum value [21] must be less than or equal to its maximum value [11]"); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minGreaterThanMaxVersion() + */ + @Test + void minGreaterThanMaxVersion() { + minGreaterThanMax(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersionGreaterThanMaxVersion() + */ + @Test + void minVersionGreaterThanMaxVersion() { + minGreaterThanMax(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersionGreaterThanMax() + */ + @Test + void minVersionGreaterThanMax() { + minGreaterThanMax(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min20() + */ + @Test + void min20() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava20() || onJava21() || onJava22() || onJava23() || onJava24() || onJava25()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion20() + */ + @Test + void minVersion20() { + min20(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#max21() + */ + @Test + void max21() { + evaluateCondition(); + assertEnabledOnCurrentJreIf( + onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() || onJava15() + || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#maxVersion21() + */ + @Test + void maxVersion21() { + max21(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min8Max21() + */ + @Test + void min8Max21() { + max21(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min17Max17() + */ + @Test + void min17Max17() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava17()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min17MaxVersion17() + */ + @Test + void min17MaxVersion17() { + min17Max17(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion17Max17() + */ + @Test + void minVersion17Max17() { + min17Max17(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion17MaxVersion17() + */ + @Test + void minVersion17MaxVersion17() { + min17Max17(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min20Max21() + */ + @Test + void min20Max21() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava20() || onJava21()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#min20MaxVersion21() + */ + @Test + void min20MaxVersion21() { + min20Max21(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion20Max21() + */ + @Test + void minVersion20Max21() { + min20Max21(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion20MaxVersion21() + */ + @Test + void minVersion20MaxVersion21() { + min20Max21(); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minVersion17MaxVersionMaxInteger() + */ + @Test + void minVersion17MaxVersionMaxInteger() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(onJava17() || onJava18() || onJava19() || onJava20() || onJava21() || onJava22() + || onJava23() || onJava24() || onJava25()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minOtherMaxOther() + */ + @Test + void minOtherMaxOther() { + evaluateCondition(); + assertEnabledOnCurrentJreIf(!onKnownVersion()); + } + + /** + * @see EnabledForJreRangeIntegrationTests#minMaxIntegerMaxMaxInteger() + */ + @Test + void minMaxIntegerMaxMaxInteger() { + minOtherMaxOther(); + } + + private void assertEnabledOnCurrentJreIf(boolean condition) { + if (condition) { + assertEnabled(); + assertReasonContains("Enabled on JRE version: " + JAVA_VERSION); + } + else { + assertDisabled(); + assertReasonContains("Disabled on JRE version: " + JAVA_VERSION); + } + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java new file mode 100644 index 000000000000..03d33d1e2632 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledForJreRangeIntegrationTests.java @@ -0,0 +1,272 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.condition.JRE.JAVA_11; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_20; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; +import static org.junit.jupiter.api.condition.JRE.JAVA_8; +import static org.junit.jupiter.api.condition.JRE.OTHER; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava10; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava11; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava12; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava13; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava14; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava15; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava16; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava17; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava18; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava19; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava20; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava21; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava22; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava23; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava8; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onJava9; +import static org.junit.jupiter.api.condition.JavaVersionPredicates.onKnownVersion; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** + * Integration tests for {@link EnabledForJreRange @EnabledForJreRange}. + * + * @since 5.6 + */ +class EnabledForJreRangeIntegrationTests { + + private static final JRE CURRENT_JRE = JRE.currentJre(); + + @Test + @Disabled("Only used in a unit test via reflection") + void enabledBecauseAnnotationIsNotPresent() { + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange + void defaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(min = JAVA_8, max = OTHER) + void effectiveJreDefaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(minVersion = 8, maxVersion = Integer.MAX_VALUE) + void effectiveVersionDefaultValues() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(min = JAVA_8) + void min8() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(minVersion = 8) + void minVersion8() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(max = OTHER) + void maxOther() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(maxVersion = Integer.MAX_VALUE) + void maxVersionMaxInteger() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(minVersion = 7) + void minVersion7() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(maxVersion = 7) + void maxVersion7() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(min = JAVA_18, minVersion = 21) + void minAndMinVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(max = JAVA_18, maxVersion = 21) + void maxAndMaxVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(min = JAVA_21, max = JAVA_11) + void minGreaterThanMax() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(min = JAVA_21, maxVersion = 11) + void minGreaterThanMaxVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(minVersion = 21, maxVersion = 11) + void minVersionGreaterThanMaxVersion() { + fail("should result in a configuration exception"); + } + + @Test + @Disabled("Only used in a unit test via reflection") + @EnabledForJreRange(minVersion = 21, max = JAVA_11) + void minVersionGreaterThanMax() { + fail("should result in a configuration exception"); + } + + @Test + @EnabledForJreRange(min = JAVA_20) + void min20() { + assertTrue(onKnownVersion()); + assertTrue(JRE.currentVersionNumber() >= 20); + assertTrue(CURRENT_JRE.compareTo(JAVA_20) >= 0); + assertTrue(CURRENT_JRE.version() >= 20); + assertFalse(onJava19()); + } + + @Test + @EnabledForJreRange(minVersion = 20) + void minVersion20() { + min20(); + } + + @Test + @EnabledForJreRange(max = JAVA_21) + void max21() { + assertTrue(onKnownVersion()); + assertTrue(JRE.currentVersionNumber() <= 21); + assertTrue(CURRENT_JRE.compareTo(JAVA_21) <= 0); + assertTrue(CURRENT_JRE.version() <= 21); + + assertTrue(onJava8() || onJava9() || onJava10() || onJava11() || onJava12() || onJava13() || onJava14() + || onJava15() || onJava16() || onJava17() || onJava18() || onJava19() || onJava20() || onJava21()); + assertFalse(onJava22()); + } + + @Test + @EnabledForJreRange(maxVersion = 21) + void maxVersion21() { + max21(); + } + + @Test + @EnabledForJreRange(min = JAVA_8, max = JAVA_21) + void min8Max21() { + max21(); + } + + @Test + @EnabledForJreRange(min = JAVA_17, max = JAVA_17) + void min17Max17() { + assertTrue(onJava17()); + } + + @Test + @EnabledForJreRange(min = JAVA_17, maxVersion = 17) + void min17MaxVersion17() { + min17Max17(); + } + + @Test + @EnabledForJreRange(minVersion = 17, max = JAVA_17) + void minVersion17Max17() { + min17Max17(); + } + + @Test + @EnabledForJreRange(minVersion = 17, maxVersion = 17) + void minVersion17MaxVersion17() { + min17Max17(); + } + + @Test + @EnabledForJreRange(min = JAVA_20, max = JAVA_21) + void min20Max21() { + assertTrue(onJava20() || onJava21()); + assertFalse(onJava17() || onJava23()); + } + + @Test + @EnabledForJreRange(min = JAVA_20, maxVersion = 21) + void min20MaxVersion21() { + min20Max21(); + } + + @Test + @EnabledForJreRange(minVersion = 20, max = JAVA_21) + void minVersion20Max21() { + min20Max21(); + } + + @Test + @EnabledForJreRange(minVersion = 20, maxVersion = 21) + void minVersion20MaxVersion21() { + min20Max21(); + } + + @Test + @EnabledForJreRange(minVersion = 17, maxVersion = Integer.MAX_VALUE) + void minVersion17MaxVersionMaxInteger() { + assertTrue(onKnownVersion()); + assertTrue(JRE.currentVersionNumber() >= 17); + } + + @Test + @EnabledForJreRange(min = OTHER, max = OTHER) + void minOtherMaxOther() { + assertFalse(onKnownVersion()); + } + + @Test + @EnabledForJreRange(minVersion = Integer.MAX_VALUE, maxVersion = Integer.MAX_VALUE) + void minMaxIntegerMaxMaxInteger() { + minOtherMaxOther(); + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java similarity index 92% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java index fc74017cdc93..152a392eabc3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionClassLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; -import org.junit.platform.commons.util.ReflectionUtils; /** * Tests for {@link EnabledIfCondition} using custom {@link ClassLoader} arrangements. @@ -40,7 +40,7 @@ void enabledWithStaticMethodInTypeFromDifferentClassLoader() throws Exception { assertThat(testClass.getClassLoader()).isSameAs(testClassLoader); ExtensionContext context = mock(); - Method annotatedMethod = ReflectionUtils.findMethod(getClass(), "enabledMethod").get(); + Method annotatedMethod = ReflectionSupport.findMethod(getClass(), "enabledMethod").get(); when(context.getElement()).thenReturn(Optional.of(annotatedMethod)); doReturn(testClass).when(context).getRequiredTestClass(); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java index de2c55c2f7fd..274d9b87a042 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java index 4dcff0018ede..db0116e38695 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java index 6e5d0a1d0ff9..641eaf922f28 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfEnvironmentVariableIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java index 6ff479f21209..4c790cbc2204 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java index 45d9ad15a657..d9391250e47f 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java index 325d42050345..e369f2168769 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledIfSystemPropertyIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java index 9dda47713c51..56c3f0431a37 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java index d79df561212b..77a9cae4b831 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/EnabledOnOsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/JRETests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/JRETests.java new file mode 100644 index 000000000000..1b92b2f9ce13 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/JRETests.java @@ -0,0 +1,166 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.condition; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.condition.JRE.JAVA_17; +import static org.junit.jupiter.api.condition.JRE.JAVA_18; +import static org.junit.jupiter.api.condition.JRE.JAVA_19; +import static org.junit.jupiter.api.condition.JRE.JAVA_20; +import static org.junit.jupiter.api.condition.JRE.JAVA_21; +import static org.junit.jupiter.api.condition.JRE.JAVA_22; +import static org.junit.jupiter.api.condition.JRE.JAVA_23; +import static org.junit.jupiter.api.condition.JRE.JAVA_24; +import static org.junit.jupiter.api.condition.JRE.JAVA_25; +import static org.junit.jupiter.api.condition.JRE.OTHER; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link JRE} + * + * @since 5.7 + */ +public class JRETests { + + private static final JRE CURRENT_JRE = JRE.currentJre(); + + @Test + @EnabledOnJre(JAVA_17) + void jre17() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_17); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(17); + } + + @Test + @EnabledOnJre(JAVA_18) + void jre18() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_18); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(18); + } + + @Test + @EnabledOnJre(JAVA_19) + void jre19() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_19); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(19); + } + + @Test + @EnabledOnJre(JAVA_20) + void jre20() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_20); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(20); + } + + @Test + @EnabledOnJre(JAVA_21) + void jre21() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_21); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(21); + } + + @Test + @EnabledOnJre(JAVA_22) + void jre22() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_21); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(22); + } + + @Test + @EnabledOnJre(JAVA_23) + void jre23() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_23); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(23); + } + + @Test + @EnabledOnJre(JAVA_24) + void jre24() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_24); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(24); + } + + @Test + @EnabledOnJre(JAVA_25) + void jre25() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(JAVA_25); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(25); + } + + @Test + @EnabledOnJre(versions = 17) + void version17() { + jre17(); + } + + @Test + @EnabledOnJre(versions = 18) + void version18() { + jre18(); + } + + @Test + @EnabledOnJre(versions = 19) + void version19() { + jre19(); + } + + @Test + @EnabledOnJre(versions = 20) + void version20() { + jre20(); + } + + @Test + @EnabledOnJre(versions = 21) + void version21() { + jre21(); + } + + @Test + @EnabledOnJre(versions = 22) + void version22() { + jre22(); + } + + @Test + @EnabledOnJre(versions = 23) + void version23() { + jre23(); + } + + @Test + @EnabledOnJre(versions = 24) + void version24() { + jre24(); + } + + @Test + @EnabledOnJre(versions = 25) + void version25() { + jre25(); + } + + @Test + @EnabledOnJre(OTHER) + void jreOther() { + assertThat(CURRENT_JRE).as("current version").isEqualTo(OTHER); + assertThat(CURRENT_JRE.version()).as("current feature version").isEqualTo(Integer.MAX_VALUE); + } + + @Test + @EnabledOnJre(versions = Integer.MAX_VALUE) + void versionMaxInteger() { + jreOther(); + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java similarity index 89% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java index eb113d684bfd..182f737fbe86 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/condition/StaticConditionMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java index 7d7038849878..d6a60aa09e08 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/CloseableResourceIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -35,6 +35,7 @@ void closesCloseableResourcesInReverseInsertOrder() { reportEntry(Map.of("1", "closed"))); } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExtensionContextParameterResolver.class) static class TestCase { @Test @@ -64,6 +65,7 @@ void exceptionsDuringCloseAreReportedAsSuppressed() { )))); } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ThrowingOnCloseExtension.class) static class ExceptionInCloseableResourceTestCase { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java similarity index 64% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java index ecbe3414db0e..34e3f284596a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExecutableInvokerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -39,32 +39,38 @@ void invokeMethodViaExtensionContext() { assertEquals(2, ExecuteTestsTwiceTestCase.testInvocations); } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExecuteTestsTwiceExtension.class) static class ExecuteTestsTwiceTestCase { static int testInvocations = 0; + @SuppressWarnings("JUnitMalformedDeclaration") @Test - void testWithResolvedParameter(TestInfo testInfo) { + void testWithResolvedParameter(TestInfo testInfo, + @ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) { assertNotNull(testInfo); + assertEquals(testInfo.getTestMethod().orElseThrow(), extensionContext.getRequiredTestMethod()); testInvocations++; } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExecuteConstructorTwiceExtension.class) static class ExecuteConstructorTwiceTestCase { static int constructorInvocations = 0; - public ExecuteConstructorTwiceTestCase(TestInfo testInfo) { + public ExecuteConstructorTwiceTestCase(TestInfo testInfo, + @ExtendWith(ExtensionContextParameterResolver.class) ExtensionContext extensionContext) { assertNotNull(testInfo); + assertEquals(testInfo.getTestClass().orElseThrow(), extensionContext.getRequiredTestClass()); constructorInvocations++; } @Test void test() { - } } @@ -84,9 +90,24 @@ static class ExecuteConstructorTwiceExtension implements BeforeAllCallback { @Override public void beforeAll(ExtensionContext context) throws Exception { context.getExecutableInvoker() // - .invoke(context.getRequiredTestClass().getConstructor(TestInfo.class)); + .invoke(context.getRequiredTestClass().getConstructor(TestInfo.class, ExtensionContext.class)); } } + static class ExtensionContextParameterResolver implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return ExtensionContext.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return extensionContext; + } + } + } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java similarity index 64% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java index 877d31cc61b5..9fb7a9efa9dd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/ExtensionComposabilityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,20 +11,28 @@ package org.junit.jupiter.api.extension; import static java.util.function.Predicate.not; -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.platform.commons.util.FunctionUtils.where; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ClassUtils; -import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for extension composability in JUnit Jupiter. @@ -43,8 +51,7 @@ class ExtensionComposabilityTests { void ensureJupiterExtensionApisAreComposable() { // 1) Find all existing top-level Extension APIs - List> extensionApis = ReflectionUtils.findAllClassesInPackage(Extension.class.getPackage().getName(), - this::isExtensionApi, name -> true); + List> extensionApis = findExtensionApis(); // 2) Determine which methods we expect the kitchen sink to implement... @@ -54,18 +61,18 @@ void ensureJupiterExtensionApisAreComposable() { .flatMap(Arrays::stream) .filter(not(Method::isSynthetic)) .filter(not(where(Method::getModifiers, Modifier::isStatic))) - .collect(toList()); + .toList(); List expectedMethodSignatures = expectedMethods.stream() .map(this::methodSignature) .sorted() - .collect(toList()); + .toList(); List expectedMethodNames = expectedMethods.stream() .map(Method::getName) .distinct() .sorted() - .collect(toList()); + .toList(); // @formatter:on // 3) Dynamically implement all Extension APIs @@ -76,20 +83,20 @@ void ensureJupiterExtensionApisAreComposable() { // @formatter:off List actualMethods = Arrays.stream(dynamicKitchenSinkExtension.getClass().getDeclaredMethods()) - .filter(ReflectionUtils::isNotStatic) - .collect(toList()); + .filter(ModifierSupport::isNotStatic) + .toList(); List actualMethodSignatures = actualMethods.stream() .map(this::methodSignature) .distinct() .sorted() - .collect(toList()); + .collect(toCollection(ArrayList::new)); List actualMethodNames = actualMethods.stream() .map(Method::getName) .distinct() .sorted() - .collect(toList()); + .collect(toCollection(ArrayList::new)); // @formatter:on // 5) Remove methods from java.lang.Object @@ -110,6 +117,34 @@ void ensureJupiterExtensionApisAreComposable() { // @formatter:on } + @TestFactory + Stream kitchenSinkExtensionImplementsAllExtensionApis() { + var declaredMethods = List.of(KitchenSinkExtension.class.getDeclaredMethods()); + return findExtensionApis().stream() // + .map(c -> dynamicContainer( // + c.getSimpleName(), // + Stream.concat( // + Stream.of( + dynamicTest("implements interface", () -> c.isAssignableFrom(KitchenSinkExtension.class))), // + Arrays.stream(c.getMethods()) // + .filter(ModifierSupport::isNotStatic).map(m -> dynamicTest( // + "overrides " + m.getName(), // + () -> assertTrue( // + declaredMethods.stream().anyMatch(it -> // + it.getName().equals(m.getName()) // + && it.getReturnType().equals(m.getReturnType()) // + && Arrays.equals(it.getParameterTypes(), m.getParameterTypes()) // + ))) // + ) // + ) // + )); + } + + private List> findExtensionApis() { + return ReflectionSupport.findAllClassesInPackage(Extension.class.getPackage().getName(), this::isExtensionApi, + name -> true); + } + private boolean isExtensionApi(Class candidate) { return candidate.isInterface() && (candidate != Extension.class) && Extension.class.isAssignableFrom(candidate); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java similarity index 54% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java index 3179d3c1124e..0d77cac0882e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/KitchenSinkExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,8 @@ package org.junit.jupiter.api.extension; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.util.Optional; import java.util.stream.Stream; @@ -58,11 +60,19 @@ public class KitchenSinkExtension implements // Miscellaneous TestWatcher, - InvocationInterceptor + InvocationInterceptor, + PreInterruptCallback // @formatter:on { + // --- Test Class Instantiation -------------------------------------------- + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + // --- Lifecycle Callbacks ------------------------------------------------- @Override @@ -159,6 +169,11 @@ public Stream provideTestTemplateInvocationContex return null; } + @Override + public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext context) { + return false; + } + // --- TestWatcher --------------------------------------------------------- @Override @@ -177,4 +192,75 @@ public void testAborted(ExtensionContext context, Throwable cause) { public void testFailed(ExtensionContext context, Throwable cause) { } + // --- InvocationInterceptor ----------------------------------------------- + + @Override + public T interceptTestClassConstructor(Invocation invocation, + ReflectiveInvocationContext> invocationContext, ExtensionContext extensionContext) + throws Throwable { + return InvocationInterceptor.super.interceptTestClassConstructor(invocation, invocationContext, + extensionContext); + } + + @Override + public void interceptBeforeAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptBeforeAllMethod(invocation, invocationContext, extensionContext); + } + + @Override + public void interceptBeforeEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptBeforeEachMethod(invocation, invocationContext, extensionContext); + } + + @Override + public void interceptTestMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptTestMethod(invocation, invocationContext, extensionContext); + } + + @Override + public T interceptTestFactoryMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + return InvocationInterceptor.super.interceptTestFactoryMethod(invocation, invocationContext, extensionContext); + } + + @Override + public void interceptTestTemplateMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptTestTemplateMethod(invocation, invocationContext, extensionContext); + } + + @SuppressWarnings("deprecation") + @Override + public void interceptDynamicTest(Invocation invocation, ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptDynamicTest(invocation, extensionContext); + } + + @Override + public void interceptDynamicTest(Invocation invocation, DynamicTestInvocationContext invocationContext, + ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptDynamicTest(invocation, invocationContext, extensionContext); + } + + @Override + public void interceptAfterEachMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptAfterEachMethod(invocation, invocationContext, extensionContext); + } + + @Override + public void interceptAfterAllMethod(Invocation invocation, + ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { + InvocationInterceptor.super.interceptAfterAllMethod(invocation, invocationContext, extensionContext); + } + + // --- PreInterruptCallback ------------------------------------------------ + + @Override + public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) + throws Exception { + + } } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java new file mode 100644 index 000000000000..b1d06036b6fc --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/MediaTypeTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.api.extension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; + +class MediaTypeTests { + + @Test + void parse() { + MediaType mediaType = MediaType.parse("text/plain"); + assertEquals("text/plain", mediaType.toString()); + } + + @Test + void create() { + MediaType mediaType = MediaType.create("application", "json"); + assertEquals("application/json", mediaType.toString()); + } + + @Test + void createWithCharset() { + MediaType mediaType = MediaType.create("application", "json", StandardCharsets.UTF_8); + assertEquals("application/json; charset=UTF-8", mediaType.toString()); + } + + @Test + void parseWithInvalidMediaType() { + var exception = assertThrows(PreconditionViolationException.class, () -> MediaType.parse("invalid")); + assertEquals("Invalid media type: 'invalid'", exception.getMessage()); + } + + @Test + void parseWithNullMediaType() { + var exception = assertThrows(PreconditionViolationException.class, () -> MediaType.parse(null)); + assertEquals("value must not be null", exception.getMessage()); + } + + @Test + void equals() { + MediaType mediaType1 = MediaType.TEXT_PLAIN; + MediaType mediaType2 = MediaType.parse("text/plain"); + MediaType mediaType3 = MediaType.parse("application/json"); + + assertEqualsAndHashCode(mediaType1, mediaType2, mediaType3); + } +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java index 178547888a2f..eec416474611 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/extension/support/TypeBasedParameterResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.6 @@ -98,7 +98,7 @@ private static ExtensionContext extensionContext() { } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionUtils.findMethod(Sample.class, methodName, parameterTypes).get(); + Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).get(); return method.getParameters()[0]; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java new file mode 100644 index 000000000000..f0e54f73424f --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/LockTests.java @@ -0,0 +1,48 @@ +/* + * 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.parallel; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; +import static org.junit.jupiter.api.parallel.ResourceLocksProvider.Lock; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link Lock}. + * + * @since 5.12 + */ +class LockTests { + + @Test + void readWriteModeSetByDefault() { + assertEquals(READ_WRITE, new Lock("a").getAccessMode()); + } + + @Test + void equalsAndHashCode() { + // @formatter:off + assertEqualsAndHashCode( + new Lock("a", READ_WRITE), + new Lock("a", READ_WRITE), + new Lock("b", READ_WRITE) + ); + assertEqualsAndHashCode( + new Lock("a", READ_WRITE), + new Lock("a", READ_WRITE), + new Lock("a", READ) + ); + // @formatter:on + } +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java new file mode 100644 index 000000000000..60e7c36b808f --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLockAnnotationTests.java @@ -0,0 +1,526 @@ +/* + * 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.parallel; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Throwables.getRootCause; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor; +import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.engine.TestExecutionResult; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource; +import org.junit.platform.testkit.engine.Event; + +/** + * Integration tests for {@link ResourceLock} and {@link ResourceLocksProvider}. + * + * @since 5.12 + */ +class ResourceLockAnnotationTests extends AbstractJupiterTestEngineTests { + + private static final UniqueId uniqueId = UniqueId.root("enigma", "foo"); + + private final JupiterConfiguration configuration = mock(); + + @BeforeEach + void setUp() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + } + + @Test + void noSharedResources() { + // @formatter:off + var classResources = getClassResources( + NoSharedResourcesTestCase.class + ); + assertThat(classResources).isEmpty(); + + var methodResources = getMethodResources( + NoSharedResourcesTestCase.class + ); + assertThat(methodResources).isEmpty(); + + var nestedClassResources = getNestedClassResources( + NoSharedResourcesTestCase.NestedClass.class + ); + assertThat(nestedClassResources).isEmpty(); + // @formatter:on + } + + @Test + void addSharedResourcesViaAnnotationValue() { + // @formatter:off + var classResources = getClassResources( + SharedResourcesViaAnnotationValueTestCase.class + ); + assertThat(classResources).containsExactlyInAnyOrder( + new ExclusiveResource("a1", LockMode.READ_WRITE), + new ExclusiveResource("a2", LockMode.READ_WRITE) + ); + + var methodResources = getMethodResources( + SharedResourcesViaAnnotationValueTestCase.class + ); + assertThat(methodResources).containsExactlyInAnyOrder( + new ExclusiveResource("a3", LockMode.READ_WRITE), + new ExclusiveResource("b1", LockMode.READ), + new ExclusiveResource("b2", LockMode.READ_WRITE) + ); + + var nestedClassResources = getNestedClassResources( + SharedResourcesViaAnnotationValueTestCase.NestedClass.class + ); + assertThat(nestedClassResources).containsExactlyInAnyOrder( + new ExclusiveResource("a3", LockMode.READ_WRITE), + new ExclusiveResource("c1", LockMode.READ) + ); + + var nestedClassMethodResources = getMethodResources( + SharedResourcesViaAnnotationValueTestCase.NestedClass.class + ); + assertThat(nestedClassMethodResources).containsExactlyInAnyOrder( + new ExclusiveResource("c2", LockMode.READ) + ); + // @formatter:on + } + + @Test + void addSharedResourcesViaAnnotationProviders() { + // @formatter:off + var classResources = getClassResources( + SharedResourcesViaAnnotationProvidersTestCase.class + ); + assertThat(classResources).containsExactlyInAnyOrder( + new ExclusiveResource("a1", LockMode.READ), + new ExclusiveResource("a2", LockMode.READ) + ); + + var methodResources = getMethodResources( + SharedResourcesViaAnnotationProvidersTestCase.class + ); + assertThat(methodResources).containsExactlyInAnyOrder( + new ExclusiveResource("b1", LockMode.READ_WRITE), + new ExclusiveResource("b2", LockMode.READ_WRITE) + ); + + var nestedClassResources = getNestedClassResources( + SharedResourcesViaAnnotationProvidersTestCase.NestedClass.class + ); + assertThat(nestedClassResources).containsExactlyInAnyOrder( + new ExclusiveResource("c1", LockMode.READ_WRITE), + new ExclusiveResource("c2", LockMode.READ) + ); + // @formatter:on + } + + @Test + void addSharedResourcesViaAnnotationValueAndProviders() { + // @formatter:off + var classResources = getClassResources( + SharedResourcesViaAnnotationValueAndProvidersTestCase.class + ); + assertThat(classResources).containsExactlyInAnyOrder( + new ExclusiveResource("a1", LockMode.READ_WRITE), + new ExclusiveResource("a3", LockMode.READ) + ); + + var methodResources = getMethodResources( + SharedResourcesViaAnnotationValueAndProvidersTestCase.class + ); + assertThat(methodResources).containsExactlyInAnyOrder( + new ExclusiveResource("a2", LockMode.READ_WRITE), + new ExclusiveResource("b1", LockMode.READ), + new ExclusiveResource("b2", LockMode.READ) + ); + + var nestedClassResources = getNestedClassResources( + SharedResourcesViaAnnotationValueAndProvidersTestCase.NestedClass.class + ); + assertThat(nestedClassResources).containsExactlyInAnyOrder( + new ExclusiveResource("a2", LockMode.READ_WRITE), + new ExclusiveResource("c1", LockMode.READ_WRITE), + new ExclusiveResource("c2", LockMode.READ_WRITE), + new ExclusiveResource("c3", LockMode.READ_WRITE) + ); + // @formatter:on + } + + @Test + void sharedResourcesHavingTheSameValueAndModeAreDeduplicated() { + // @formatter:off + var methodResources = getMethodResources( + SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase.class + ); + assertThat(methodResources).containsExactlyInAnyOrder( + new ExclusiveResource("a1", LockMode.READ_WRITE) + ); + // @formatter:on + } + + @Test + void sharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicated() { + // @formatter:off + var methodResources = getMethodResources( + SharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicatedTestCase.class + ); + assertThat(methodResources).containsExactlyInAnyOrder( + new ExclusiveResource("a1", LockMode.READ), + new ExclusiveResource("a1", LockMode.READ_WRITE) + ); + // @formatter:on + } + + static Stream> testMethodsCanNotDeclareSharedResourcesForChildrenArguments() { + // @formatter:off + return Stream.of( + TestCanNotDeclareSharedResourcesForChildrenTestCase.class, + ParameterizedTestCanNotDeclareSharedResourcesForChildrenTestCase.class, + RepeatedTestCanNotDeclareSharedResourcesForChildrenTestCase.class, + TestFactoryCanNotDeclareSharedResourcesForChildrenTestCase.class + ); + // @formatter:on + } + + @ParameterizedTest + @MethodSource("testMethodsCanNotDeclareSharedResourcesForChildrenArguments") + void testMethodsCanNotDeclareSharedResourcesForChildren(Class testClass) { + var messageTemplate = "'ResourceLockTarget.CHILDREN' is not supported for methods. Invalid method: %s"; + assertThrowsJunitExceptionWithMessage( // + testClass, // + messageTemplate.formatted(getDeclaredTestMethod(testClass)) // + ); + } + + @Test + void emptyAnnotation() { + // @formatter:off + var classResources = getClassResources( + EmptyAnnotationTestCase.class + ); + assertThat(classResources).isEmpty(); + + var methodResources = getMethodResources( + EmptyAnnotationTestCase.class + ); + assertThat(methodResources).isEmpty(); + + var nestedClassResources = getNestedClassResources( + EmptyAnnotationTestCase.NestedClass.class + ); + assertThat(nestedClassResources).isEmpty(); + // @formatter:on + } + + private Set getClassResources(Class testClass) { + return getClassTestDescriptor(testClass).getExclusiveResources(); + } + + private ClassTestDescriptor getClassTestDescriptor(Class testClass) { + return new ClassTestDescriptor(uniqueId, testClass, configuration); + } + + private Set getMethodResources(Class testClass) { + var descriptor = new TestMethodTestDescriptor( // + uniqueId, testClass, getDeclaredTestMethod(testClass), List::of, configuration // + ); + descriptor.setParent(getClassTestDescriptor(testClass)); + return descriptor.getExclusiveResources(); + } + + private static Method getDeclaredTestMethod(Class testClass) { + try { + return testClass.getDeclaredMethod("test"); + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private Set getNestedClassResources(Class testClass) { + var descriptor = new NestedClassTestDescriptor(uniqueId, testClass, List::of, configuration); + descriptor.setParent(getClassTestDescriptor(testClass.getEnclosingClass())); + return descriptor.getExclusiveResources(); + } + + private void assertThrowsJunitExceptionWithMessage(Class testClass, String message) { + // @formatter:off + var events = executeTestsForClass(testClass).allEvents(); + assertThat(events.filter(finishedWithFailure(instanceOf(JUnitException.class))::matches)) + .hasSize(1) + .map(Event::getPayload) + .map(payload -> (TestExecutionResult) payload.orElseThrow()) + .map(payload -> getRootCause(payload.getThrowable().orElseThrow())) + .first() + .is(instanceOf(JUnitException.class)) + .has(message(message)); + // @formatter:on + } + + // ------------------------------------------------------------------------- + + @SuppressWarnings("JUnitMalformedDeclaration") + static class NoSharedResourcesTestCase { + + @Test + void test() { + } + + @Nested + class NestedClass { + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock("a1") + @ResourceLock(value = "a2", mode = ResourceAccessMode.READ_WRITE) + @ResourceLock(value = "a3", mode = ResourceAccessMode.READ_WRITE, target = ResourceLockTarget.CHILDREN) + static class SharedResourcesViaAnnotationValueTestCase { + + @Test + @ResourceLock(value = "b1", mode = ResourceAccessMode.READ) + @ResourceLock(value = "b2", target = ResourceLockTarget.SELF) + void test() { + } + + @Nested + @ResourceLock(value = "c1", mode = ResourceAccessMode.READ) + @ResourceLock(value = "c2", mode = ResourceAccessMode.READ, target = ResourceLockTarget.CHILDREN) + class NestedClass { + + @Test + void test() { + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock(providers = { // + SharedResourcesViaAnnotationProvidersTestCase.FirstClassLevelProvider.class, // + SharedResourcesViaAnnotationProvidersTestCase.SecondClassLevelProvider.class // + }) + static class SharedResourcesViaAnnotationProvidersTestCase { + + @Test + @ResourceLock(providers = MethodLevelProvider.class) + void test() { + } + + @Nested + @ResourceLock(providers = NestedClassLevelProvider.class) + class NestedClass { + } + + static class FirstClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForClass(Class testClass) { + return Set.of(new Lock("a1", ResourceAccessMode.READ)); + } + } + + static class SecondClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForClass(Class testClass) { + return Set.of(new Lock("a2", ResourceAccessMode.READ)); + } + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return Set.of(new Lock("b1")); + } + } + + static class MethodLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return Set.of(new Lock("b2")); + } + } + + static class NestedClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + return Set.of(new Lock("c1"), new Lock("c2", ResourceAccessMode.READ)); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock( // + value = "a1", // + providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.FirstClassLevelProvider.class // + ) + @ResourceLock( // + value = "a2", // + target = ResourceLockTarget.CHILDREN, // + providers = SharedResourcesViaAnnotationValueAndProvidersTestCase.SecondClassLevelProvider.class // + ) + static class SharedResourcesViaAnnotationValueAndProvidersTestCase { + + @Test + @ResourceLock(value = "b1", mode = ResourceAccessMode.READ) + void test() { + } + + @Nested + @ResourceLock("c1") + @ResourceLock(providers = NestedClassLevelProvider.class) + class NestedClass { + } + + static class FirstClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForClass(Class testClass) { + return Set.of(new Lock("a3", ResourceAccessMode.READ)); + } + } + + static class SecondClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return Set.of(new Lock("b2", ResourceAccessMode.READ)); + } + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + return Set.of(new Lock("c2")); + } + } + + static class NestedClassLevelProvider implements ResourceLocksProvider { + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + return Set.of(new Lock("c3")); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock( // + value = "a1", // + target = ResourceLockTarget.CHILDREN, // + providers = SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase.Provider.class // + ) + static class SharedResourcesHavingTheSameValueAndModeAreDeduplicatedTestCase { + + @Test + @ResourceLock(value = "a1") + void test() { + } + + static class Provider implements ResourceLocksProvider { + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + return Set.of(new Lock("a1")); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock(value = "a1", mode = ResourceAccessMode.READ_WRITE, target = ResourceLockTarget.CHILDREN) + static class SharedResourcesHavingTheSameValueButDifferentModeAreNotDeduplicatedTestCase { + + @Test + @ResourceLock(value = "a1", mode = ResourceAccessMode.READ) + void test() { + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class TestCanNotDeclareSharedResourcesForChildrenTestCase { + + @Test + @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) + void test() { + } + } + + static class ParameterizedTestCanNotDeclareSharedResourcesForChildrenTestCase { + + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3 }) + @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) + void test() { + } + } + + static class RepeatedTestCanNotDeclareSharedResourcesForChildrenTestCase { + + @RepeatedTest(5) + @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) + void test() { + } + } + + static class TestFactoryCanNotDeclareSharedResourcesForChildrenTestCase { + + @TestFactory + @ResourceLock(value = "a1", target = ResourceLockTarget.CHILDREN) + Stream test() { + return Stream.of(DynamicTest.dynamicTest("Dynamic test", () -> { + })); + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock + static class EmptyAnnotationTestCase { + + @Test + @ResourceLock + void test() { + } + + @Nested + @ResourceLock + class NestedClass { + } + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java new file mode 100644 index 000000000000..f5bc7a17b77b --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/parallel/ResourceLocksProviderTests.java @@ -0,0 +1,315 @@ +/* + * 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.parallel; + +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.testkit.engine.Event; + +/** + * Integration tests for {@link ResourceLocksProvider}. + * + * @since 5.12 + */ +class ResourceLocksProviderTests extends AbstractJupiterTestEngineTests { + + @Test + void classLevelProvider() { + var events = execute(ClassLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + + @Test + void nestedClassLevelProvider() { + var events = execute(NestedClassLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + + @Test + void methodLevelProvider() { + var events = execute(MethodLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + + @Test + void methodLevelProviderInNestedClass() { + var events = execute(MethodLevelProviderInNestedClassTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + + @Test + void providesAccessToRuntimeEnclosingInstances() { + var events = execute(SubClassLevelProviderTestCase.class); + assertThat(events.filter(event(test(), finishedSuccessfully())::matches)).hasSize(2); + } + + private Stream execute(Class testCase) { + return executeTestsForClass(testCase).allEvents().stream(); + } + + // ------------------------------------------------------------------------- + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock(providers = ClassLevelProviderTestCase.Provider.class) + static class ClassLevelProviderTestCase { + + @Test + void test() { + assertTrue(Provider.isProvideForClassCalled, "'provideForClass' was not called"); + assertTrue(Provider.isProvideForTestMethodCalled, "'provideForMethod(test)' was not called"); + } + + @Nested + class NestedClass { + + @Test + void nestedTest() { + assertTrue(Provider.isProvideForNestedClassCalled, "'provideForNestedClass' was not called"); + // @formatter:off + assertTrue( + Provider.isProvideForNestedTestMethodCalled, + "'provideForMethod(nestedTest)' was not called" + ); + // @formatter:on + } + } + + @AfterAll + static void afterAll() { + Provider.isProvideForClassCalled = false; + Provider.isProvideForTestMethodCalled = false; + + Provider.isProvideForNestedClassCalled = false; + Provider.isProvideForNestedTestMethodCalled = false; + } + + static class Provider implements ResourceLocksProvider { + + private static boolean isProvideForClassCalled = false; + private static boolean isProvideForTestMethodCalled = false; + + private static boolean isProvideForNestedClassCalled = false; + private static boolean isProvideForNestedTestMethodCalled = false; + + private Class testClass; + + @Override + public Set provideForClass(Class testClass) { + this.testClass = testClass; + isProvideForClassCalled = true; + assertThat(testClass).isAssignableTo(ClassLevelProviderTestCase.class); + return emptySet(); + } + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + isProvideForNestedClassCalled = true; + assertEquals(List.of(this.testClass), enclosingInstanceTypes); + assertEquals(ClassLevelProviderTestCase.NestedClass.class, testClass); + return emptySet(); + } + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + if (ClassLevelProviderTestCase.class.isAssignableFrom(testClass)) { + assertEquals(List.of(), enclosingInstanceTypes); + assertEquals("test", testMethod.getName()); + isProvideForTestMethodCalled = true; + return emptySet(); + } + if (testClass == ClassLevelProviderTestCase.NestedClass.class) { + assertEquals(List.of(this.testClass), enclosingInstanceTypes); + assertEquals("nestedTest", testMethod.getName()); + isProvideForNestedTestMethodCalled = true; + return emptySet(); + } + fail("Unexpected test class: " + testClass); + return emptySet(); + } + } + } + + static class SubClassLevelProviderTestCase extends ClassLevelProviderTestCase { + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class NestedClassLevelProviderTestCase { + + @Test + void test() { + } + + @Nested + @ResourceLock(providers = NestedClassLevelProviderTestCase.Provider.class) + class NestedClass { + + @Test + void nestedTest() { + assertTrue(Provider.isProvideForNestedClassCalled, "'provideForNestedClass' was not called"); + assertTrue(Provider.isProvideForMethodCalled, "'provideForMethod' was not called"); + } + } + + @AfterAll + static void afterAll() { + Provider.isProvideForNestedClassCalled = false; + Provider.isProvideForMethodCalled = false; + } + + static class Provider implements ResourceLocksProvider { + + private static boolean isProvideForNestedClassCalled = false; + + private static boolean isProvideForMethodCalled = false; + + @Override + public Set provideForClass(Class testClass) { + fail("'provideForClass' should not be called"); + return emptySet(); + } + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + isProvideForNestedClassCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); + assertEquals(NestedClass.class, testClass); + return emptySet(); + } + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + isProvideForMethodCalled = true; + assertEquals(List.of(NestedClassLevelProviderTestCase.class), enclosingInstanceTypes); + assertEquals(NestedClassLevelProviderTestCase.NestedClass.class, testClass); + assertEquals("nestedTest", testMethod.getName()); + return emptySet(); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class MethodLevelProviderTestCase { + + @Test + @ResourceLock(providers = MethodLevelProviderTestCase.Provider.class) + void test() { + assertTrue(Provider.isProvideForMethodCalled, "'provideForMethod' was not called"); + } + + @Nested + class NestedClass { + + @Test + void nestedTest() { + } + } + + @AfterAll + static void afterAll() { + Provider.isProvideForMethodCalled = false; + } + + static class Provider implements ResourceLocksProvider { + + private static boolean isProvideForMethodCalled = false; + + @Override + public Set provideForClass(Class testClass) { + fail("'provideForClass' should not be called"); + return emptySet(); + } + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + fail("'provideForNestedClass' should not be called"); + return emptySet(); + } + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + isProvideForMethodCalled = true; + assertEquals(List.of(), enclosingInstanceTypes); + assertEquals(MethodLevelProviderTestCase.class, testClass); + assertEquals("test", testMethod.getName()); + return emptySet(); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class MethodLevelProviderInNestedClassTestCase { + + @Test + void test() { + } + + @Nested + class NestedClass { + + @Test + @ResourceLock(providers = MethodLevelProviderInNestedClassTestCase.Provider.class) + void nestedTest() { + assertTrue(Provider.isProvideForMethodCalled, "'provideForMethod' was not called"); + } + } + + @AfterAll + static void afterAll() { + Provider.isProvideForMethodCalled = false; + } + + static class Provider implements ResourceLocksProvider { + + private static boolean isProvideForMethodCalled = false; + + @Override + public Set provideForClass(Class testClass) { + fail("'provideForClass' should not be called"); + return emptySet(); + } + + @Override + public Set provideForNestedClass(List> enclosingInstanceTypes, Class testClass) { + fail("'provideForNestedClass' should not be called"); + return emptySet(); + } + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { + isProvideForMethodCalled = true; + assertEquals(List.of(MethodLevelProviderInNestedClassTestCase.class), enclosingInstanceTypes); + assertEquals(MethodLevelProviderInNestedClassTestCase.NestedClass.class, testClass); + assertEquals("nestedTest", testMethod.getName()); + return emptySet(); + } + } + } +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java index 0347d7be2303..1bd6484957f7 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssertionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java index f7183446af82..ffc55bca0505 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/api/subpackage/SubclassedAssumptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java similarity index 78% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java index 75a92cc6bf3e..3ec1035ebb5c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AbstractJupiterTestEngineTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import java.util.Set; @@ -21,6 +22,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; @@ -38,7 +40,11 @@ protected EngineExecutionResults executeTestsForClass(Class testClass) { } protected EngineExecutionResults executeTests(DiscoverySelector... selectors) { - return executeTests(request().selectors(selectors).build()); + return executeTests(request().selectors(selectors).outputDirectoryProvider(dummyOutputDirectoryProvider())); + } + + protected EngineExecutionResults executeTests(LauncherDiscoveryRequestBuilder builder) { + return executeTests(builder.build()); } protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) { @@ -46,7 +52,8 @@ protected EngineExecutionResults executeTests(LauncherDiscoveryRequest request) } protected TestDescriptor discoverTests(DiscoverySelector... selectors) { - return discoverTests(request().selectors(selectors).build()); + return discoverTests( + request().selectors(selectors).outputDirectoryProvider(dummyOutputDirectoryProvider()).build()); } protected TestDescriptor discoverTests(LauncherDiscoveryRequest request) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java index 9b54101ced9e..701515ed967b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/AtypicalJvmMethodNameTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java index 1176fea22381..69d19154ef21 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeAllAndAfterAllComposedAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -39,6 +39,7 @@ void beforeAllAndAfterAllAsMetaAnnotations() { assertThat(methodsInvoked).containsExactly("beforeAll", "test", "afterAll"); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @CustomBeforeAll diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java index 60091c7ae4a8..c59264075b7b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/BeforeEachAndAfterEachComposedAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -39,6 +39,7 @@ void beforeEachAndAfterEachAsMetaAnnotations() { assertThat(methodsInvoked).containsExactly("beforeEach", "test", "afterEach"); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @CustomBeforeEach diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java index aae9b97c1bc2..57fdb111c7a7 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultExecutionModeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -126,6 +126,7 @@ private T firstChild(TestDescriptor engineDescriptor, .orElseGet(() -> fail("No child of type " + testDescriptorClass + " found")); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @Test @@ -155,6 +156,7 @@ static class ConcurrentTestCase extends TestCase { static class SameThreadTestCase extends TestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") static class OuterTestCase { @Nested class LevelOne { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java index 7fb690877718..1561fbb76cce 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DefaultMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -200,6 +200,7 @@ default void afterAll() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class GenericTestCaseWithDefaultMethod implements GenericTestInterface { @Test @@ -210,6 +211,7 @@ void test(Double number) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class GenericTestCaseWithOverriddenDefaultMethod implements GenericTestInterface { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DisabledTests.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/DisabledTests.java index 62217371196b..0e956d97ff87 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DisabledTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DisabledTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -50,6 +50,7 @@ void executeTestsWithDisabledTestMethods() throws Exception { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @Disabled static class DisabledTestClassTestCase { @@ -59,6 +60,7 @@ void disabledTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class DisabledTestMethodsTestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java index 17e57d80ac61..30dcea34760c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/DynamicNodeGenerationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java index 80e730fa8566..e1b460a292a1 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ExceptionHandlingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -248,6 +248,7 @@ void cleanUpExceptions() { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class FailureTestCase { static Optional exceptionToThrowInBeforeAll = Optional.empty(); @@ -309,6 +310,7 @@ void abortedTest() { } + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(PER_METHOD) @ExtendWith(ThrowingAfterAllCallback.class) static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerMethodLifecycle { @@ -322,6 +324,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(PER_CLASS) @ExtendWith(ThrowingAfterAllCallback.class) static class TestCaseWithInvalidConstructorAndThrowingAfterAllCallbackAndPerClassLifecycle { @@ -334,6 +337,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ThrowingBeforeAllCallback.class) @ExtendWith(ThrowingAfterAllCallback.class) static class TestCaseWithThrowingBeforeAllAndAfterAllCallbacks { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java index be2d03755a6c..6f1b05b6261e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/FailedAssumptionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -42,6 +42,7 @@ void assumptionViolatedExceptionInBeforeAll() { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class TestAbortedExceptionInBeforeAllTestCase { @BeforeAll @@ -54,6 +55,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class AssumptionViolatedExceptionInBeforeAllTestCase { @BeforeAll diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java similarity index 87% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java index a7efbed7e73a..e1d7a47cfce6 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/InvalidLifecycleMethodConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -73,6 +73,7 @@ private void assertContainerFailed(Class invalidTestClass) { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @Test @@ -80,9 +81,11 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidNonStaticBeforeAllMethod { // must be static + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void beforeAll() { } @@ -92,9 +95,11 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidNonStaticAfterAllMethod { // must be static + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void afterAll() { } @@ -104,9 +109,11 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidStaticBeforeEachMethod { // must NOT be static + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeEach static void beforeEach() { } @@ -116,9 +123,11 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithInvalidStaticAfterEachMethod { // must NOT be static + @SuppressWarnings("JUnitMalformedDeclaration") @AfterEach static void afterEach() { } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java index bc07210ac92d..dd242d20132a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/JupiterTestEngineBasicTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java index f9e7f0b3ec89..39597094df51 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/LifecycleMethodOverridingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java index fd841d06e23e..6aa1fc0e1c84 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/MultipleTestableAnnotationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -42,8 +42,10 @@ void testAndRepeatedTest(@TrackLogRecords LogRecordListener listener) { // @formatter:on } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @Test @RepeatedTest(1) void testAndRepeatedTest(RepetitionInfo repetitionInfo) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java index 2fa1b21f524f..634aa46c34de 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedTestClassesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -190,6 +190,7 @@ private void assertNestedCycle(Class start, Class from, Class to) { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithNesting { @Test @@ -210,6 +211,7 @@ void failing() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithDoubleNesting { static int beforeTopCount = 0; @@ -283,6 +285,7 @@ void failing() { interface InterfaceWithNestedClass { + @SuppressWarnings("JUnitMalformedDeclaration") @Nested class NestedInInterface { @@ -334,6 +337,7 @@ class ConcreteInner2 extends AbstractSuperClass { static class AbstractOuterClass { } + @SuppressWarnings("JUnitMalformedDeclaration") static class OuterClass extends AbstractOuterClass { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java index 032d9bb33df7..3b3602e09fb8 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithInheritanceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java index c5f7f808ba9d..b73cee268861 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NestedWithSeparateInheritanceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java similarity index 85% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java index abc364ea5147..c00cac6960dd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/NonVoidTestableMethodIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,12 +21,14 @@ class NonVoidTestableMethodIntegrationTests { void valid() { } + @SuppressWarnings("JUnitMalformedDeclaration") @Test int invalidMethodReturningPrimitive() { fail("This method should never have been called."); return 1; } + @SuppressWarnings("JUnitMalformedDeclaration") @Test String invalidMethodReturningObject() { fail("This method should never have been called."); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java index 5aa8eab7bbf2..d0669f81cbe4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/OverloadedTestMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -64,6 +64,7 @@ void executeTestCaseWithOverloadedMethodsWithSingleMethodThatAcceptsArgumentsSel assertTrue(first.isPresent()); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/RecordTests.java similarity index 88% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/RecordTests.java index 43930deffd6f..74ef3bc0d803 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/RecordTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/RecordTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,6 +23,7 @@ void recordsAreTestClasses() { .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); } + @SuppressWarnings("JUnitMalformedDeclaration") record TestCase() { @Test diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java new file mode 100644 index 000000000000..7d69e5d8c932 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/ReportingTests.java @@ -0,0 +1,156 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; +import static org.junit.platform.testkit.engine.EventConditions.fileEntry; +import static org.junit.platform.testkit.engine.EventConditions.reportEntry; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.engine.reporting.FileEntry; + +/** + * @since 5.0 + */ +class ReportingTests extends AbstractJupiterTestEngineTests { + + @ParameterizedTest + @CsvSource(textBlock = """ + PER_CLASS, 1, 7, 5 + PER_METHOD, 0, 9, 7 + """) + void reportAndFileEntriesArePublished(Lifecycle lifecycle, int containerEntries, int testReportEntries, + int testFileEntries, @TempDir Path tempDir) { + var request = request() // + .selectors(selectClass(MyReportingTestCase.class)) // + .configurationParameter(DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, lifecycle.name()) // + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(tempDir)); + + var results = executeTests(request); + + results // + .containerEvents() // + .assertStatistics(stats -> stats // + .started(2) // + .succeeded(2) // + .reportingEntryPublished(containerEntries) // + .fileEntryPublished(containerEntries)); + + results // + .testEvents() // + .assertStatistics(stats -> stats // + .started(2) // + .succeeded(2) // + .reportingEntryPublished(testReportEntries) // + .fileEntryPublished(testFileEntries)) // + .assertThatEvents() // + .haveExactly(2, reportEntry(Map.of("value", "@BeforeEach"))) // + .haveExactly(2, fileEntry(nameAndContent("beforeEach", MediaType.TEXT_PLAIN_UTF_8))) // + .haveExactly(1, reportEntry(Map.of())) // + .haveExactly(1, reportEntry(Map.of("user name", "dk38"))) // + .haveExactly(1, reportEntry(Map.of("value", "message"))) // + .haveExactly(1, fileEntry(nameAndContent("succeedingTest", MediaType.APPLICATION_OCTET_STREAM))) // + .haveExactly(2, reportEntry(Map.of("value", "@AfterEach"))) // + .haveExactly(2, fileEntry(nameAndContent("afterEach", MediaType.TEXT_PLAIN_UTF_8))); + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class MyReportingTestCase { + + public MyReportingTestCase(TestReporter reporter) { + // Reported on class-level for PER_CLASS lifecycle and on method-level for PER_METHOD lifecycle + reporter.publishEntry("Constructor"); + reporter.publishFile("constructor", MediaType.TEXT_PLAIN_UTF_8, + file -> Files.writeString(file, "constructor")); + } + + @BeforeEach + void beforeEach(TestReporter reporter) { + reporter.publishEntry("@BeforeEach"); + reporter.publishFile("beforeEach", MediaType.TEXT_PLAIN_UTF_8, + file -> Files.writeString(file, "beforeEach")); + } + + @AfterEach + void afterEach(TestReporter reporter) { + reporter.publishEntry("@AfterEach"); + reporter.publishFile("afterEach", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "afterEach")); + } + + @Test + void succeedingTest(TestReporter reporter) { + reporter.publishEntry(Map.of()); + reporter.publishEntry("user name", "dk38"); + reporter.publishEntry("message"); + reporter.publishFile("succeedingTest", MediaType.APPLICATION_OCTET_STREAM, + file -> Files.writeString(file, "succeedingTest")); + } + + @Test + void invalidReportData(TestReporter reporter) { + + // Maps + Map map = new HashMap<>(); + + map.put("key", null); + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); + + map.clear(); + map.put(null, "value"); + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(map)); + + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((Map) null)); + + // Key-Value pair + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry(null, "bar")); + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry("foo", null)); + + // Value + assertThrows(PreconditionViolationException.class, () -> reporter.publishEntry((String) null)); + } + + } + + private static Predicate nameAndContent(String expectedName, MediaType mediaType) { + Predicate filePredicate = file -> { + try { + return Path.of(expectedName).equals(file.getFileName()) && expectedName.equals(Files.readString(file)); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + return fileEntry -> filePredicate.test(fileEntry.getPath()) // + && mediaType.toString().equals(fileEntry.getMediaType().orElse(null)); + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/SealedClassTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/SealedClassTests.java index bcbf37408c68..899cf5333280 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/SealedClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/SealedClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java index 3782fe7a9d5e..18459ff98194 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StandardTestClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -118,6 +118,7 @@ void testsFailWhenAfterEachFails() { assertTrue(TestCaseWithFailingAfter.testExecuted, "test executed?"); } + @SuppressWarnings("JUnitMalformedDeclaration") static class MyStandardTestCase { static int countBefore1 = 0; @@ -175,6 +176,7 @@ void testAbortedJUnit4Legacy() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class FirstOfTwoTestCases { @Test @@ -194,6 +196,7 @@ void failingTest() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class SecondOfTwoTestCases { @Test @@ -213,6 +216,7 @@ void succeedingTest3() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithFailingBefore { static int countBefore = 0; @@ -233,6 +237,7 @@ void test2() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithFailingAfter { static boolean testExecuted = false; diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java index 16549a1c4c1a..47ef8231cc38 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/StaticNestedBeforeAllAndAfterAllMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -49,6 +49,7 @@ void staticBeforeAllAndAfterAllMethodsInNestedTestClass() { ); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @BeforeAll diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java index 7768b76a49df..7db63a5e3021 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestClassInheritanceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -157,6 +157,7 @@ void superTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class LocalTestCase extends AbstractTestCase { boolean throwExceptionInAfterMethod = false; @@ -252,6 +253,7 @@ static void afterAll2() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase3 extends TestCase2 { @BeforeAll diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java index e800c3fede1c..fdb7e512ce75 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -143,6 +143,7 @@ private void performAssertions(Class testClass, Map configPar // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(PER_METHOD) static class ExplicitInstancePerTestMethodTestCase { @@ -168,6 +169,7 @@ static void afterAllStatic() { * {@code @AfterAll} methods are static, even though there is no explicit * {@code @TestInstance} declaration. */ + @SuppressWarnings("JUnitMalformedDeclaration") static class AssumedInstancePerTestMethodTestCase { @BeforeAll @@ -187,6 +189,7 @@ static void afterAllStatic() { } + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(PER_CLASS) static class ExplicitInstancePerClassTestCase { @@ -212,8 +215,10 @@ void afterAll(TestInfo testInfo) { * {@code @AfterAll} methods are non-static, even though there is no * explicit {@code @TestInstance} declaration. */ + @SuppressWarnings("JUnitMalformedDeclaration") static class AssumedInstancePerClassTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void beforeAll(TestInfo testInfo) { methodsInvoked.add("beforeAll"); @@ -224,6 +229,7 @@ void test() { methodsInvoked.add("test"); } + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void afterAll(TestInfo testInfo) { methodsInvoked.add("afterAll"); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java index 6a1f1cf843f5..c4fd1d8087c3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleKotlinTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java index 6c8b87fed8f1..91676086da86 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; +import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -677,6 +677,7 @@ private static String concat(String... args) { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstanceTrackingExtension.class) // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) @@ -752,6 +753,7 @@ static class SubInstancePerClassTestCase extends InstancePerClassTestCase { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstanceTrackingExtension.class) // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) @@ -817,6 +819,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstanceTrackingExtension.class) @TestInstance(Lifecycle.PER_CLASS) static class InstancePerClassOuterTestCase { @@ -892,6 +895,7 @@ void afterAll(TestInfo testInfo) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstanceTrackingExtension.class) // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) @@ -992,8 +996,7 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext contex trackLifecycle(context); assertThat(context.getTestInstance()).isNotPresent(); assertNotNull(testInstance); - instanceMap.put(postProcessTestInstanceKey(context.getRequiredTestClass()), - DefaultTestInstances.of(testInstance)); + instanceMap.put(postProcessTestInstanceKey(testInstance.getClass()), DefaultTestInstances.of(testInstance)); } @Override diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java index 84ad9e49c65d..b9427e78e6e6 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/TestTemplateInvocationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -353,9 +353,40 @@ void templateWithSupportingProviderButNoInvocationsReportsFailure() { wrappedInContainerEvents(MyTestTemplateTestCase.class, // event(container("templateWithSupportingProviderButNoInvocations"), started()), // event(container("templateWithSupportingProviderButNoInvocations"), - finishedWithFailure(message("None of the supporting TestTemplateInvocationContextProviders [" - + InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName() - + "] provided a non-empty stream"))))); + finishedWithFailure(message( + "Provider [%s] did not provide any invocation contexts, but was expected to do so. ".formatted( + InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName()) + + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this."))))); + } + + @Test + void templateWithSupportingProviderAllowingNoInvocationsDoesNotFail() { + LauncherDiscoveryRequest request = request().selectors( + selectMethod(MyTestTemplateTestCase.class, "templateWithSupportingProviderAllowingNoInvocations")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, + event(container("templateWithSupportingProviderAllowingNoInvocations"), started()), + event(container("templateWithSupportingProviderAllowingNoInvocations"), finishedSuccessfully()))); + } + + @Test + void templateWithMixedProvidersNoInvocationReportsFailure() { + LauncherDiscoveryRequest request = request().selectors(selectMethod(MyTestTemplateTestCase.class, + "templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing")).build(); + + EngineExecutionResults executionResults = executeTests(request); + + executionResults.allEvents().assertEventsMatchExactly( // + wrappedInContainerEvents(MyTestTemplateTestCase.class, // + event(container("templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing"), started()), // + event(container("templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing"), + finishedWithFailure(message( + "Provider [%s] did not provide any invocation contexts, but was expected to do so. ".formatted( + InvocationContextProviderThatSupportsEverythingButProvidesNothing.class.getSimpleName()) + + "You may override mayReturnZeroTestTemplateInvocationContexts() to allow this."))))); } @Test @@ -470,6 +501,19 @@ void templateWithSupportingProviderButNoInvocations() { fail("never called"); } + @ExtendWith(InvocationContextProviderThatSupportsEverythingAllowsProvideNothing.class) + @TestTemplate + void templateWithSupportingProviderAllowingNoInvocations() { + fail("never called"); + } + + @ExtendWith(InvocationContextProviderThatSupportsEverythingButProvidesNothing.class) + @ExtendWith(InvocationContextProviderThatSupportsEverythingAllowsProvideNothing.class) + @TestTemplate + void templateWithMultipleProvidersAllowingAndRestrictingToProvideNothing() { + fail("never called"); + } + @ExtendWith(InvocationContextProviderWithCloseableStream.class) @TestTemplate void templateWithCloseableStream() { @@ -769,6 +813,25 @@ public Stream provideTestTemplateInvocationContex } } + private static class InvocationContextProviderThatSupportsEverythingAllowsProvideNothing + implements TestTemplateInvocationContextProvider { + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.empty(); + } + + @Override + public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext extensionContext) { + return true; + } + } + private static class InvocationContextProviderWithCloseableStream implements TestTemplateInvocationContextProvider { private static AtomicBoolean streamClosed = new AtomicBoolean(false); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java index fc4a7616f884..823ccbd591ca 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNonGenericTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java index 2f3f06bbb4d7..6357ab8f9ad4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/AbstractNumberTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java index 87a9c02cdd17..2483cb2e4e8a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/BridgeMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java index 373b6a0e5bb0..9aa56c376e4a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithBridgeMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java index 52a8afcfa9b5..60dff28fa5d7 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/ChildWithoutBridgeMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java index 9da2a5c20721..7bf5fdea49f9 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java index 4c66e26c42c9..0e83f8ac3c02 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/NumberTestGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java index 2aed5ec13288..0807c14f3252 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/bridge/PackagePrivateParent.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java index 5d1214a68972..7f961da52cc6 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/CachingJupiterConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java similarity index 90% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java index a6c1d9919e56..ba7daa0fe4bb 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/DefaultJupiterConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,6 +17,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -44,20 +45,20 @@ class DefaultJupiterConfigurationTests { @Test void getDefaultTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> new DefaultJupiterConfiguration(null)); + () -> new DefaultJupiterConfiguration(null, dummyOutputDirectoryProvider())); assertThat(exception).hasMessage("ConfigurationParameters must not be null"); } @Test void getDefaultTestInstanceLifecycleWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider()); Lifecycle lifecycle = configuration.getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getDefaultTempDirCleanupModeWithNoConfigParamSet() { - JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock()); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider()); CleanupMode cleanupMode = configuration.getDefaultTempDirCleanupMode(); assertThat(cleanupMode).isEqualTo(ALWAYS); } @@ -82,7 +83,8 @@ void shouldGetDefaultDisplayNameGeneratorWithConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.of(CustomDisplayNameGenerator.class.getName())); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); @@ -94,7 +96,8 @@ void shouldGetStandardAsDefaultDisplayNameGeneratorWithoutConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_DISPLAY_NAME_GENERATOR_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); DisplayNameGenerator defaultDisplayNameGenerator = configuration.getDefaultDisplayNameGenerator(); @@ -106,7 +109,8 @@ void shouldGetNothingAsDefaultTestMethodOrderWithoutConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_TEST_METHOD_ORDER_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); final Optional defaultTestMethodOrder = configuration.getDefaultTestMethodOrderer(); @@ -118,7 +122,8 @@ void shouldGetDefaultTempDirFactorySupplierWithConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.of(CustomFactory.class.getName())); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); @@ -138,7 +143,8 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { ConfigurationParameters parameters = mock(); String key = Constants.DEFAULT_TEMP_DIR_FACTORY_PROPERTY_NAME; when(parameters.get(key)).thenReturn(Optional.empty()); - JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters); + JupiterConfiguration configuration = new DefaultJupiterConfiguration(parameters, + dummyOutputDirectoryProvider()); Supplier supplier = configuration.getDefaultTempDirFactorySupplier(); @@ -148,7 +154,8 @@ void shouldGetStandardAsDefaultTempDirFactorySupplierWithoutConfigParamSet() { private void assertDefaultConfigParam(String configValue, Lifecycle expected) { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.ofNullable(configValue)); - Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams).getDefaultTestInstanceLifecycle(); + Lifecycle lifecycle = new DefaultJupiterConfiguration(configParams, + dummyOutputDirectoryProvider()).getDefaultTestInstanceLifecycle(); assertThat(lifecycle).isEqualTo(expected); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java index c5bb4939fc5c..0f8f6b630c38 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/config/InstantiatingConfigurationParameterConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java similarity index 68% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java index 3e3c7c411d01..6b00ad5b9282 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/CustomDisplayNameGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.descriptor; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; @@ -22,12 +23,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return "nested-class-display-name"; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return "method-display-name"; } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java similarity index 89% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java index 905747468b45..7a9ca9a4a165 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/DisplayNameUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -70,7 +71,7 @@ void shouldGetDisplayNameFromSupplierIfNoDisplayNameAnnotationPresent() { @Nested class ClassDisplayNameSupplierTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { @@ -115,12 +116,12 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { @Nested class NestedClassDisplayNameTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, StandardDisplayNameTestCase.class, configuration); assertThat(displayName.get()).isEqualTo(StandardDisplayNameTestCase.class.getSimpleName()); @@ -129,7 +130,7 @@ void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() { @Test void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); @@ -138,7 +139,7 @@ void shouldGetDisplayNameFromDefaultDisplayNameGenerator() { @Test void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass( + Supplier displayName = DisplayNameUtils.createDisplayNameSupplierForNestedClass(List::of, NullDisplayNameTestCase.NestedTestCase.class, configuration); assertThat(displayName.get()).isEqualTo("nested-class-display-name"); @@ -148,14 +149,14 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() { @Nested class MethodDisplayNameTests { - private JupiterConfiguration configuration = mock(); + private final JupiterConfiguration configuration = mock(); @Test void shouldGetDisplayNameFromDisplayNameGenerationAnnotation() throws Exception { when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); Method method = MyTestCase.class.getDeclaredMethod("test1"); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(StandardDisplayNameTestCase.class, - method, configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, + StandardDisplayNameTestCase.class, method, configuration); assertThat(displayName).isEqualTo("test1()"); } @@ -165,8 +166,8 @@ void shouldGetDisplayNameFromDefaultNameGenerator() throws Exception { Method method = MyTestCase.class.getDeclaredMethod("test1"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NotDisplayNameTestCase.class, method, - configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NotDisplayNameTestCase.class, + method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @@ -176,8 +177,8 @@ void shouldFallbackOnDefaultDisplayNameGeneratorWhenNullIsGenerated() throws Exc Method method = NullDisplayNameTestCase.class.getDeclaredMethod("test"); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new CustomDisplayNameGenerator()); - String displayName = DisplayNameUtils.determineDisplayNameForMethod(NullDisplayNameTestCase.class, method, - configuration); + String displayName = DisplayNameUtils.determineDisplayNameForMethod(List::of, NullDisplayNameTestCase.class, + method, configuration); assertThat(displayName).isEqualTo("method-display-name"); } @@ -216,6 +217,7 @@ static class NotDisplayNameTestCase { class NestedTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayNameGeneration(value = NullDisplayNameGenerator.class) static class NullDisplayNameTestCase { @@ -237,12 +239,13 @@ public String generateDisplayNameForClass(Class testClass) { } @Override - public String generateDisplayNameForNestedClass(Class nestedClass) { + public String generateDisplayNameForNestedClass(List> enclosingInstanceTypes, Class nestedClass) { return null; } @Override - public String generateDisplayNameForMethod(Class testClass, Method testMethod) { + public String generateDisplayNameForMethod(List> enclosingInstanceTypes, Class testClass, + Method testMethod) { return null; } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java new file mode 100644 index 000000000000..48682ff17e7a --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionContextTests.java @@ -0,0 +1,493 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Named.named; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.extension.PreInterruptCallback; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.engine.config.DefaultJupiterConfiguration; +import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.jupiter.engine.execution.DefaultTestInstances; +import org.junit.jupiter.engine.extension.ExtensionRegistry; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.EngineExecutionListener; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; +import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; +import org.mockito.ArgumentCaptor; + +/** + * Unit tests for concrete implementations of {@link ExtensionContext}: + * {@link JupiterEngineExtensionContext}, {@link ClassExtensionContext}, and + * {@link MethodExtensionContext}. + * + * @since 5.0 + */ +public class ExtensionContextTests { + + private final JupiterConfiguration configuration = mock(); + private final ExtensionRegistry extensionRegistry = mock(); + + @BeforeEach + void setUp() { + when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); + when(configuration.getDefaultExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + when(configuration.getDefaultClassesExecutionMode()).thenReturn(ExecutionMode.SAME_THREAD); + when(configuration.getOutputDirectoryProvider()).thenReturn(dummyOutputDirectoryProvider()); + } + + @Test + void fromJupiterEngineDescriptor() { + var engineTestDescriptor = new JupiterEngineDescriptor(UniqueId.root("engine", "junit-jupiter"), configuration); + + try (var engineContext = new JupiterEngineExtensionContext(null, engineTestDescriptor, configuration, + extensionRegistry)) { + // @formatter:off + assertAll("engineContext", + () -> assertThat(engineContext.getElement()).isEmpty(), + () -> assertThat(engineContext.getTestClass()).isEmpty(), + () -> assertThat(engineContext.getTestInstance()).isEmpty(), + () -> assertThat(engineContext.getTestMethod()).isEmpty(), + () -> assertThrows(PreconditionViolationException.class, engineContext::getRequiredTestClass), + () -> assertThrows(PreconditionViolationException.class, engineContext::getRequiredTestInstance), + () -> assertThrows(PreconditionViolationException.class, engineContext::getRequiredTestMethod), + () -> assertThat(engineContext.getDisplayName()).isEqualTo(engineTestDescriptor.getDisplayName()), + () -> assertThat(engineContext.getParent()).isEmpty(), + () -> assertThat(engineContext.getRoot()).isSameAs(engineContext), + () -> assertThat(engineContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD), + () -> assertThat(engineContext.getExtensions(PreInterruptCallback.class)).isEmpty() + ); + // @formatter:on + } + } + + @Test + @SuppressWarnings("resource") + void fromClassTestDescriptor() { + var nestedClassDescriptor = nestedClassDescriptor(); + var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); + + var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, configuration, + extensionRegistry, null); + + // @formatter:off + assertAll("outerContext", + () -> assertThat(outerExtensionContext.getElement()).contains(OuterClass.class), + () -> assertThat(outerExtensionContext.getTestClass()).contains(OuterClass.class), + () -> assertThat(outerExtensionContext.getTestInstance()).isEmpty(), + () -> assertThat(outerExtensionContext.getTestMethod()).isEmpty(), + () -> assertThat(outerExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), + () -> assertThrows(PreconditionViolationException.class, outerExtensionContext::getRequiredTestInstance), + () -> assertThrows(PreconditionViolationException.class, outerExtensionContext::getRequiredTestMethod), + () -> assertThat(outerExtensionContext.getDisplayName()).isEqualTo(outerClassDescriptor.getDisplayName()), + () -> assertThat(outerExtensionContext.getParent()).isEmpty(), + () -> assertThat(outerExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD), + () -> assertThat(outerExtensionContext.getExtensions(PreInterruptCallback.class)).isEmpty() + ); + // @formatter:on + + var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor, + configuration, extensionRegistry, null); + assertThat(nestedExtensionContext.getParent()).containsSame(outerExtensionContext); + } + + @Test + void ExtensionContext_With_ExtensionRegistry_getExtensions() { + var classTestDescriptor = nestedClassDescriptor(); + try (var ctx = new ClassExtensionContext(null, null, classTestDescriptor, configuration, extensionRegistry, + null)) { + + Extension ext = mock(); + when(extensionRegistry.getExtensions(Extension.class)).thenReturn(List.of(ext)); + + assertThat(ctx.getExtensions(Extension.class)).isEqualTo(List.of(ext)); + } + } + + @Test + @SuppressWarnings("resource") + void tagsCanBeRetrievedInExtensionContext() { + var nestedClassDescriptor = nestedClassDescriptor(); + var outerClassDescriptor = outerClassDescriptor(nestedClassDescriptor); + var methodTestDescriptor = methodDescriptor(); + outerClassDescriptor.addChild(methodTestDescriptor); + + var outerExtensionContext = new ClassExtensionContext(null, null, outerClassDescriptor, configuration, + extensionRegistry, null); + + assertThat(outerExtensionContext.getTags()).containsExactly("outer-tag"); + assertThat(outerExtensionContext.getRoot()).isSameAs(outerExtensionContext); + + var nestedExtensionContext = new ClassExtensionContext(outerExtensionContext, null, nestedClassDescriptor, + configuration, extensionRegistry, null); + assertThat(nestedExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "nested-tag"); + assertThat(nestedExtensionContext.getRoot()).isSameAs(outerExtensionContext); + + var methodExtensionContext = new MethodExtensionContext(outerExtensionContext, null, methodTestDescriptor, + configuration, extensionRegistry, new OpenTest4JAwareThrowableCollector()); + methodExtensionContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); + assertThat(methodExtensionContext.getTags()).containsExactlyInAnyOrder("outer-tag", "method-tag"); + assertThat(methodExtensionContext.getRoot()).isSameAs(outerExtensionContext); + } + + @Test + @SuppressWarnings("resource") + void fromMethodTestDescriptor() { + var methodTestDescriptor = methodDescriptor(); + var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); + var engineDescriptor = new JupiterEngineDescriptor(UniqueId.forEngine("junit-jupiter"), configuration); + engineDescriptor.addChild(classTestDescriptor); + + Object testInstance = new OuterClass(); + var testMethod = methodTestDescriptor.getTestMethod(); + + var engineExtensionContext = new JupiterEngineExtensionContext(null, engineDescriptor, configuration, + extensionRegistry); + var classExtensionContext = new ClassExtensionContext(engineExtensionContext, null, classTestDescriptor, + configuration, extensionRegistry, null); + var methodExtensionContext = new MethodExtensionContext(classExtensionContext, null, methodTestDescriptor, + configuration, extensionRegistry, new OpenTest4JAwareThrowableCollector()); + methodExtensionContext.setTestInstances(DefaultTestInstances.of(testInstance)); + + // @formatter:off + assertAll("methodContext", + () -> assertThat(methodExtensionContext.getElement()).contains(testMethod), + () -> assertThat(methodExtensionContext.getTestClass()).contains(OuterClass.class), + () -> assertThat(methodExtensionContext.getTestInstance()).contains(testInstance), + () -> assertThat(methodExtensionContext.getTestMethod()).contains(testMethod), + () -> assertThat(methodExtensionContext.getRequiredTestClass()).isEqualTo(OuterClass.class), + () -> assertThat(methodExtensionContext.getRequiredTestInstance()).isEqualTo(testInstance), + () -> assertThat(methodExtensionContext.getRequiredTestMethod()).isEqualTo(testMethod), + () -> assertThat(methodExtensionContext.getDisplayName()).isEqualTo(methodTestDescriptor.getDisplayName()), + () -> assertThat(methodExtensionContext.getParent()).contains(classExtensionContext), + () -> assertThat(methodExtensionContext.getRoot()).isSameAs(engineExtensionContext), + () -> assertThat(methodExtensionContext.getExecutionMode()).isEqualTo(ExecutionMode.SAME_THREAD) + ); + // @formatter:on + } + + @Test + @SuppressWarnings("resource") + void reportEntriesArePublishedToExecutionListener() { + var classTestDescriptor = outerClassDescriptor(null); + var engineExecutionListener = spy(EngineExecutionListener.class); + ExtensionContext extensionContext = new ClassExtensionContext(null, engineExecutionListener, + classTestDescriptor, configuration, extensionRegistry, null); + + var map1 = Collections.singletonMap("key", "value"); + var map2 = Collections.singletonMap("other key", "other value"); + + extensionContext.publishReportEntry(map1); + extensionContext.publishReportEntry(map2); + extensionContext.publishReportEntry("3rd key", "third value"); + extensionContext.publishReportEntry("status message"); + + var entryCaptor = ArgumentCaptor.forClass(ReportEntry.class); + verify(engineExecutionListener, times(4)) // + .reportingEntryPublished(eq(classTestDescriptor), entryCaptor.capture()); + + var reportEntry1 = entryCaptor.getAllValues().get(0); + var reportEntry2 = entryCaptor.getAllValues().get(1); + var reportEntry3 = entryCaptor.getAllValues().get(2); + var reportEntry4 = entryCaptor.getAllValues().get(3); + + assertEquals(map1, reportEntry1.getKeyValuePairs()); + assertEquals(map2, reportEntry2.getKeyValuePairs()); + assertEquals("third value", reportEntry3.getKeyValuePairs().get("3rd key")); + assertEquals("status message", reportEntry4.getKeyValuePairs().get("value")); + } + + @Test + void fileEntriesArePublishedToExecutionListener(@TempDir Path tempDir) { + var engineExecutionListener = mock(EngineExecutionListener.class); + var classTestDescriptor = outerClassDescriptor(null); + var extensionContext = createExtensionContextForFilePublishing(tempDir, engineExecutionListener, + classTestDescriptor); + + extensionContext.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8, + file -> Files.writeString(file, "Test 1")); + extensionContext.publishDirectory("test2", dir -> { + Files.writeString(dir.resolve("nested1.txt"), "Nested content 1"); + Files.writeString(dir.resolve("nested2.txt"), "Nested content 2"); + }); + + var entryCaptor = ArgumentCaptor.forClass(FileEntry.class); + verify(engineExecutionListener, times(2)) // + .fileEntryPublished(eq(classTestDescriptor), entryCaptor.capture()); + var fileEntries = entryCaptor.getAllValues(); + + var fileEntry1 = fileEntries.getFirst(); + assertThat(fileEntry1.getPath()).isEqualTo(tempDir.resolve("OuterClass/test1.txt")); + assertThat(fileEntry1.getMediaType()).contains(MediaType.TEXT_PLAIN_UTF_8.toString()); + + var fileEntry2 = fileEntries.get(1); + assertThat(fileEntry2.getPath()).isEqualTo(tempDir.resolve("OuterClass/test2")); + assertThat(fileEntry2.getMediaType()).isEmpty(); + assertThat(fileEntry2.getPath().resolve("nested1.txt")).usingCharset(UTF_8).hasContent("Nested content 1"); + assertThat(fileEntry2.getPath().resolve("nested2.txt")).usingCharset(UTF_8).hasContent("Nested content 2"); + } + + @Test + void failsWhenAttemptingToPublishFileWithPathSeparators(@TempDir Path tempDir) { + var extensionContext = createExtensionContextForFilePublishing(tempDir); + var name = "test" + File.separator + "subDir"; + + var exception = assertThrows(PreconditionViolationException.class, () -> extensionContext.publishFile(name, + MediaType.APPLICATION_OCTET_STREAM, __ -> fail("should not be called"))); + assertThat(exception).hasMessage("name must not contain path separators: " + name); + } + + @Test + void failsWhenAttemptingToPublishDirectoryWithPathSeparators(@TempDir Path tempDir) { + var extensionContext = createExtensionContextForFilePublishing(tempDir); + var name = "test" + File.separator + "subDir"; + + var exception = assertThrows(PreconditionViolationException.class, + () -> extensionContext.publishDirectory(name, __ -> fail("should not be called"))); + assertThat(exception).hasMessage("name must not contain path separators: " + name); + } + + @Test + void failsWhenAttemptingToPublishMissingFiles(@TempDir Path tempDir) { + var extensionContext = createExtensionContextForFilePublishing(tempDir); + + var exception = assertThrows(PreconditionViolationException.class, + () -> extensionContext.publishFile("test", MediaType.APPLICATION_OCTET_STREAM, Files::deleteIfExists)); + assertThat(exception).hasMessage("Published path must exist: " + tempDir.resolve("OuterClass").resolve("test")); + } + + @Test + void failsWhenAttemptingToPublishMissingDirectory(@TempDir Path tempDir) { + var extensionContext = createExtensionContextForFilePublishing(tempDir); + + var exception = assertThrows(PreconditionViolationException.class, + () -> extensionContext.publishDirectory("test", Files::delete)); + assertThat(exception).hasMessage("Published path must exist: " + tempDir.resolve("OuterClass").resolve("test")); + } + + @Test + void failsWhenAttemptingToPublishDirectoriesAsRegularFiles(@TempDir Path tempDir) { + var extensionContext = createExtensionContextForFilePublishing(tempDir); + + var exception = assertThrows(PreconditionViolationException.class, + () -> extensionContext.publishFile("test", MediaType.APPLICATION_OCTET_STREAM, Files::createDirectory)); + assertThat(exception).hasMessage( + "Published path must be a regular file: " + tempDir.resolve("OuterClass").resolve("test")); + } + + @Test + void failsWhenAttemptingToPublishRegularFilesAsDirectories(@TempDir Path tempDir) { + var extensionContext = createExtensionContextForFilePublishing(tempDir); + + var exception = assertThrows(PreconditionViolationException.class, + () -> extensionContext.publishDirectory("test", dir -> { + Files.delete(dir); + Files.createFile(dir); + })); + assertThat(exception).hasMessage( + "Published path must be a directory: " + tempDir.resolve("OuterClass").resolve("test")); + } + + private ExtensionContext createExtensionContextForFilePublishing(Path tempDir) { + return createExtensionContextForFilePublishing(tempDir, mock(EngineExecutionListener.class), + outerClassDescriptor(null)); + } + + private ExtensionContext createExtensionContextForFilePublishing(Path tempDir, + EngineExecutionListener engineExecutionListener, ClassTestDescriptor classTestDescriptor) { + when(configuration.getOutputDirectoryProvider()) // + .thenReturn(hierarchicalOutputDirectoryProvider(tempDir)); + return new ClassExtensionContext(null, engineExecutionListener, classTestDescriptor, configuration, + extensionRegistry, null); + } + + @Test + @SuppressWarnings("resource") + void usingStore() { + var methodTestDescriptor = methodDescriptor(); + var classTestDescriptor = outerClassDescriptor(methodTestDescriptor); + ExtensionContext parentContext = new ClassExtensionContext(null, null, classTestDescriptor, configuration, + extensionRegistry, null); + var childContext = new MethodExtensionContext(parentContext, null, methodTestDescriptor, configuration, + extensionRegistry, new OpenTest4JAwareThrowableCollector()); + childContext.setTestInstances(DefaultTestInstances.of(new OuterClass())); + + var childStore = childContext.getStore(Namespace.GLOBAL); + var parentStore = parentContext.getStore(Namespace.GLOBAL); + + final Object key1 = "key 1"; + final var value1 = "a value"; + childStore.put(key1, value1); + assertEquals(value1, childStore.get(key1)); + assertEquals(value1, childStore.remove(key1)); + assertNull(childStore.get(key1)); + + childStore.put(key1, value1); + assertEquals(value1, childStore.get(key1)); + assertEquals(value1, childStore.remove(key1, String.class)); + assertNull(childStore.get(key1)); + + final Object key2 = "key 2"; + final var value2 = "other value"; + assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2)); + assertEquals(value2, childStore.getOrComputeIfAbsent(key2, key -> value2, String.class)); + assertEquals(value2, childStore.get(key2)); + assertEquals(value2, childStore.get(key2, String.class)); + + final Object parentKey = "parent key"; + final var parentValue = "parent value"; + parentStore.put(parentKey, parentValue); + assertEquals(parentValue, childStore.getOrComputeIfAbsent(parentKey, k -> "a different value")); + assertEquals(parentValue, childStore.get(parentKey)); + } + + @ParameterizedTest + @MethodSource("extensionContextFactories") + void configurationParameter(Function extensionContextFactory) { + JupiterConfiguration echo = new DefaultJupiterConfiguration(new EchoParameters(), + dummyOutputDirectoryProvider()); + var key = "123"; + var expected = Optional.of(key); + + var context = extensionContextFactory.apply(echo); + + assertEquals(expected, context.getConfigurationParameter(key)); + } + + static List>> extensionContextFactories() { + ExtensionRegistry extensionRegistry = mock(); + var testClass = ExtensionContextTests.class; + return List.of( // + named("engine", (JupiterConfiguration configuration) -> { + var engineUniqueId = UniqueId.parse("[engine:junit-jupiter]"); + var engineDescriptor = new JupiterEngineDescriptor(engineUniqueId, configuration); + return new JupiterEngineExtensionContext(null, engineDescriptor, configuration, extensionRegistry); + }), // + named("class", (JupiterConfiguration configuration) -> { + var classUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]"); + var classTestDescriptor = new ClassTestDescriptor(classUniqueId, testClass, configuration); + return new ClassExtensionContext(null, null, classTestDescriptor, configuration, extensionRegistry, + null); + }), // + named("method", (JupiterConfiguration configuration) -> { + var method = ReflectionSupport.findMethod(testClass, "extensionContextFactories").orElseThrow(); + var methodUniqueId = UniqueId.parse("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]"); + var methodTestDescriptor = new TestMethodTestDescriptor(methodUniqueId, testClass, method, List::of, + configuration); + return new MethodExtensionContext(null, null, methodTestDescriptor, configuration, extensionRegistry, + null); + }) // + ); + } + + private NestedClassTestDescriptor nestedClassDescriptor() { + return new NestedClassTestDescriptor(UniqueId.root("nested-class", "NestedClass"), OuterClass.NestedClass.class, + List::of, configuration); + } + + private ClassTestDescriptor outerClassDescriptor(TestDescriptor child) { + var classTestDescriptor = new ClassTestDescriptor(UniqueId.root("class", "OuterClass"), OuterClass.class, + configuration); + if (child != null) { + classTestDescriptor.addChild(child); + } + return classTestDescriptor; + } + + private TestMethodTestDescriptor methodDescriptor() { + try { + return new TestMethodTestDescriptor(UniqueId.root("method", "aMethod"), OuterClass.class, + OuterClass.class.getDeclaredMethod("aMethod"), List::of, configuration); + } + catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + @Tag("outer-tag") + static class OuterClass { + + @Tag("nested-tag") + static class NestedClass { + } + + @Tag("method-tag") + void aMethod() { + } + } + + private static class EchoParameters implements ConfigurationParameters { + + @Override + public Optional get(String key) { + return Optional.of(key); + } + + @Override + public Optional getBoolean(String key) { + throw new UnsupportedOperationException("getBoolean(String) should not be called"); + } + + @SuppressWarnings({ "deprecation", "RedundantSuppression" }) + @Override + public int size() { + throw new UnsupportedOperationException("size() should not be called"); + } + + @Override + public Set keySet() { + throw new UnsupportedOperationException("keySet() should not be called"); + + } + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java new file mode 100644 index 000000000000..2ede30ff8fc4 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/ExtensionsUtilsTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.descriptor; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.engine.extension.ExtensionRegistrar; + +/** + * Tests for {@link ExtensionUtils}. + * + * @since 5.11.3 + */ +class ExtensionsUtilsTests { + + @Test + void registerExtensionsViaStaticFields() throws Exception { + Field field = TestCase.class.getDeclaredField("staticField"); + ExtensionRegistrar registrar = mock(); + ExtensionUtils.registerExtensionsFromStaticFields(registrar, TestCase.class); + verify(registrar).registerExtension(Extension1.class); + verify(registrar).registerExtension(Extension2.class); + verify(registrar).registerExtension(TestCase.staticField, field); + } + + @Test + @SuppressWarnings("unchecked") + void registerExtensionsViaInstanceFields() throws Exception { + Class testClass = TestCase.class; + Field field = testClass.getDeclaredField("instanceField"); + ExtensionRegistrar registrar = mock(); + ExtensionUtils.registerExtensionsFromInstanceFields(registrar, testClass); + verify(registrar).registerExtension(Extension1.class); + verify(registrar).registerExtension(Extension2.class); + verify(registrar).registerUninitializedExtension(eq(testClass), eq(field), any(Function.class)); + } + + static class Extension1 implements Extension { + } + + static class Extension2 implements Extension { + } + + static class Extension3 implements Extension { + } + + static class Extension4 implements Extension { + } + + @Retention(RetentionPolicy.RUNTIME) + @ExtendWith(Extension1.class) + @ExtendWith(Extension2.class) + @interface UseCustomExtensions { + } + + static class TestCase { + + @UseCustomExtensions + @RegisterExtension + static Extension3 staticField = new Extension3(); + + @UseCustomExtensions + @RegisterExtension + Extension4 instanceField = new Extension4(); + + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java index b81a9f658514..053d03498d22 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/JupiterTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.engine.descriptor; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; @@ -114,7 +113,7 @@ void constructFromClassWithInvalidAfterEachDeclaration() { void constructFromMethod() throws Exception { Class testClass = TestCase.class; Method testMethod = testClass.getDeclaredMethod("test"); - TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, + TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, testClass, testMethod, List::of, configuration); assertEquals(uniqueId, descriptor.getUniqueId()); @@ -128,14 +127,14 @@ void constructFromMethodWithAnnotations() throws Exception { JupiterTestDescriptor classDescriptor = new ClassTestDescriptor(uniqueId, TestCase.class, configuration); Method testMethod = TestCase.class.getDeclaredMethod("foo"); TestMethodTestDescriptor methodDescriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); classDescriptor.addChild(methodDescriptor); assertEquals(testMethod, methodDescriptor.getTestMethod()); assertEquals("custom test name", methodDescriptor.getDisplayName(), "display name:"); assertEquals("foo()", methodDescriptor.getLegacyReportingName(), "legacy name:"); - List tags = methodDescriptor.getTags().stream().map(TestTag::getName).collect(toList()); + List tags = methodDescriptor.getTags().stream().map(TestTag::getName).toList(); assertThat(tags).containsExactlyInAnyOrder("inherited-class-level-tag", "classTag1", "classTag2", "methodTag1", "methodTag2"); } @@ -144,7 +143,7 @@ void constructFromMethodWithAnnotations() throws Exception { void constructFromMethodWithCustomTestAnnotation() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("customTestAnnotation"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("custom name", descriptor.getDisplayName(), "display name:"); @@ -156,7 +155,7 @@ void constructFromMethodWithCustomTestAnnotation() throws Exception { void constructFromMethodWithParameters() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String.class, BigDecimal.class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String, BigDecimal)", descriptor.getDisplayName(), "display name"); @@ -167,7 +166,7 @@ void constructFromMethodWithParameters() throws Exception { void constructFromMethodWithPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[])", descriptor.getDisplayName(), "display name"); @@ -178,7 +177,7 @@ void constructFromMethodWithPrimitiveArrayParameter() throws Exception { void constructFromMethodWithObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[])", descriptor.getDisplayName(), "display name"); @@ -189,7 +188,7 @@ void constructFromMethodWithObjectArrayParameter() throws Exception { void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", int[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(int[][][][][])", descriptor.getDisplayName(), "display name"); @@ -200,7 +199,7 @@ void constructFromMethodWithMultidimensionalPrimitiveArrayParameter() throws Exc void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Exception { Method testMethod = TestCase.class.getDeclaredMethod("test", String[][][][][].class); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, TestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); assertEquals("test(String[][][][][])", descriptor.getDisplayName(), "display name"); @@ -211,7 +210,7 @@ void constructFromMethodWithMultidimensionalObjectArrayParameter() throws Except void constructFromInheritedMethod() throws Exception { Method testMethod = ConcreteTestCase.class.getMethod("theTest"); TestMethodTestDescriptor descriptor = new TestMethodTestDescriptor(uniqueId, ConcreteTestCase.class, testMethod, - configuration); + List::of, configuration); assertEquals(testMethod, descriptor.getTestMethod()); @@ -231,7 +230,7 @@ void shouldTakeCustomMethodNameDescriptorFromConfigurationIfPresent() { assertEquals("class-display-name", descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals("nested-class-display-name", descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); @@ -250,7 +249,7 @@ void defaultDisplayNamesForTestClasses() { assertEquals(getClass().getSimpleName(), descriptor.getDisplayName()); assertEquals(getClass().getName(), descriptor.getLegacyReportingName()); - descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, configuration); + descriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, List::of, configuration); assertEquals(NestedTestCase.class.getSimpleName(), descriptor.getDisplayName()); assertEquals(NestedTestCase.class.getName(), descriptor.getLegacyReportingName()); @@ -270,7 +269,7 @@ void enclosingClassesAreDerivedFromParent() { ClassBasedTestDescriptor parentDescriptor = new ClassTestDescriptor(uniqueId, StaticTestCase.class, configuration); ClassBasedTestDescriptor nestedDescriptor = new NestedClassTestDescriptor(uniqueId, NestedTestCase.class, - configuration); + List::of, configuration); assertThat(parentDescriptor.getEnclosingTestClasses()).isEmpty(); assertThat(nestedDescriptor.getEnclosingTestClasses()).isEmpty(); @@ -296,7 +295,7 @@ private static abstract class AbstractTestCase { @Tag("classTag1") @Tag("classTag2") @DisplayName("custom class name") - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "JUnitMalformedDeclaration" }) private static class TestCase extends AbstractTestCase { void test() { @@ -331,9 +330,11 @@ void customTestAnnotation() { } + @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidBeforeAllMethod { // must be static + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void beforeAll() { } @@ -344,9 +345,11 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidAfterAllMethod { // must be static + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void afterAll() { } @@ -357,9 +360,11 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidBeforeEachMethod { // must NOT be static + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeEach static void beforeEach() { } @@ -370,9 +375,11 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") private static class TestCaseWithInvalidAfterEachMethod { // must NOT be static + @SuppressWarnings("JUnitMalformedDeclaration") @AfterEach static void afterEach() { } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java similarity index 92% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java index 85a8fed7160b..719f335679bf 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/LifecycleMethodUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.engine.descriptor; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -131,13 +130,14 @@ void findAfterAllMethodsWithLifeCyclePerClassAndRequiringStatic() { } private static List namesOf(List methods) { - return methods.stream().map(Method::getName).collect(toList()); + return methods.stream().map(Method::getName).toList(); } } class TestCaseWithStandardLifecycle { + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll void one() { } @@ -158,10 +158,12 @@ void eleven() { void twelve() { } + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void five() { } + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll void six() { } @@ -191,21 +193,25 @@ void eight() { class TestCaseWithNonVoidLifecyleMethods { + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeEach String aa() { return null; } + @SuppressWarnings("JUnitMalformedDeclaration") @AfterEach int bb() { return 1; } + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll Double cc() { return null; } + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll String dd() { return ""; diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java index 8841c01c3efa..500c36113a7d 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/NamespaceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java index b16f765f0e91..6ad584b728df 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestFactoryTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ import java.io.File; import java.lang.reflect.Method; import java.net.URI; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; @@ -147,7 +148,7 @@ void before() throws Exception { Method testMethod = CustomStreamTestCase.class.getDeclaredMethod("customStream"); descriptor = new TestFactoryTestDescriptor(UniqueId.forEngine("engine"), CustomStreamTestCase.class, - testMethod, jupiterConfiguration); + testMethod, List::of, jupiterConfiguration); when(extensionContext.getTestMethod()).thenReturn(Optional.of(testMethod)); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java similarity index 80% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java index 2bbdf1321a95..186604bc98a9 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestInstanceLifecycleUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.engine.Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME; import static org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils.getTestInstanceLifecycle; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -50,7 +51,8 @@ class TestInstanceLifecycleUtilsTests { @Test void getTestInstanceLifecyclePreconditions() { PreconditionViolationException exception = assertThrows(PreconditionViolationException.class, - () -> getTestInstanceLifecycle(null, new DefaultJupiterConfiguration(mock()))); + () -> getTestInstanceLifecycle(null, + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider()))); assertThat(exception).hasMessage("testClass must not be null"); exception = assertThrows(PreconditionViolationException.class, @@ -60,7 +62,8 @@ void getTestInstanceLifecyclePreconditions() { @Test void getTestInstanceLifecycleWithNoConfigParamSet() { - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_METHOD); } @@ -68,7 +71,8 @@ void getTestInstanceLifecycleWithNoConfigParamSet() { void getTestInstanceLifecycleWithConfigParamSet() { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); - Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), new DefaultJupiterConfiguration(configParams)); + Lifecycle lifecycle = getTestInstanceLifecycle(getClass(), + new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @@ -76,21 +80,24 @@ void getTestInstanceLifecycleWithConfigParamSet() { void getTestInstanceLifecycleWithLocalConfigThatOverridesCustomDefaultSetViaConfigParam() { ConfigurationParameters configParams = mock(); when(configParams.get(KEY)).thenReturn(Optional.of(PER_CLASS.name().toLowerCase())); - Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, new DefaultJupiterConfiguration(configParams)); + Lifecycle lifecycle = getTestInstanceLifecycle(TestCase.class, + new DefaultJupiterConfiguration(configParams, dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_METHOD); } @Test void getTestInstanceLifecycleFromMetaAnnotationWithNoConfigParamSet() { Class testClass = BaseMetaAnnotatedTestCase.class; - Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(testClass, + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_CLASS); } @Test void getTestInstanceLifecycleFromSpecializedClassWithNoConfigParamSet() { Class testClass = SpecializedTestCase.class; - Lifecycle lifecycle = getTestInstanceLifecycle(testClass, new DefaultJupiterConfiguration(mock())); + Lifecycle lifecycle = getTestInstanceLifecycle(testClass, + new DefaultJupiterConfiguration(mock(), dummyOutputDirectoryProvider())); assertThat(lifecycle).isEqualTo(PER_CLASS); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java index 0e0834f3e105..65b2bf268688 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateInvocationTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import static org.mockito.Mockito.when; import java.lang.reflect.Method; +import java.util.List; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -34,7 +35,7 @@ void invocationsDoNotDeclareExclusiveResources() throws Exception { JupiterConfiguration configuration = mock(); when(configuration.getDefaultDisplayNameGenerator()).thenReturn(new DisplayNameGenerator.Standard()); TestTemplateTestDescriptor parent = new TestTemplateTestDescriptor(UniqueId.root("segment", "template"), - testClass, testTemplateMethod, configuration); + testClass, testTemplateMethod, List::of, configuration); TestTemplateInvocationContext invocationContext = mock(); when(invocationContext.getDisplayName(anyInt())).thenReturn("invocation"); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java index ecc410f2af2f..c0dab4d6a66e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/TestTemplateTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.List; import java.util.Set; import org.junit.jupiter.api.DisplayNameGenerator; @@ -45,7 +46,7 @@ void inheritsTagsFromParent() throws Exception { TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getTags()).containsExactlyInAnyOrder(TestTag.create("foo"), TestTag.create("bar"), @@ -63,7 +64,7 @@ void shouldUseCustomDisplayNameGeneratorIfPresentFromConfiguration() throws Exce TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("method-display-name"); @@ -80,7 +81,7 @@ void shouldUseStandardDisplayNameGeneratorIfConfigurationNotPresent() throws Exc TestTemplateTestDescriptor testDescriptor = new TestTemplateTestDescriptor( parentUniqueId.append("tmp", "testTemplate()"), MyTestCase.class, - MyTestCase.class.getDeclaredMethod("testTemplate"), jupiterConfiguration); + MyTestCase.class.getDeclaredMethod("testTemplate"), List::of, jupiterConfiguration); parent.addChild(testDescriptor); assertThat(testDescriptor.getDisplayName()).isEqualTo("testTemplate()"); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java similarity index 88% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java index 8d34c3705df0..a990fdf2b21a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class1WithTestCases.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java similarity index 88% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java index 49f87adb29e6..0327f7ed288b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/Class2WithTestCases.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java similarity index 77% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java index 0090eb336fcc..aec19a0891a4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithStaticInnerTestCases.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,6 +17,7 @@ */ public class ClassWithStaticInnerTestCases { + @SuppressWarnings("JUnitMalformedDeclaration") public static class ShouldBeDiscovered { @Test @@ -24,7 +25,7 @@ void test1() { } } - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "JUnitMalformedDeclaration" }) private static class ShouldNotBeDiscovered { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java similarity index 87% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java index e1c29f8e0c64..bb5682af0c86 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/descriptor/subpackage/ClassWithoutTestCases.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java index 7e938d9eb954..6c74b09dbbb4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoverySelectorResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,6 @@ package org.junit.jupiter.engine.discovery; import static java.util.Collections.singleton; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; @@ -72,7 +71,7 @@ import org.junit.jupiter.engine.descriptor.subpackage.ClassWithStaticInnerTestCases; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.SelectorResolutionResult; import org.junit.platform.engine.TestDescriptor; @@ -457,7 +456,7 @@ void packageResolutionUsingDefaultPackage() throws Exception { List uniqueIds = uniqueIds(); assertThat(uniqueIds)// .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// - .contains(uniqueIdForClass(ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get())); + .contains(uniqueIdForClass(ReflectionSupport.tryToLoadClass("DefaultPackageTestCase").get())); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); @@ -481,7 +480,7 @@ void classpathResolution() throws Exception { List uniqueIds = uniqueIds(); assertThat(uniqueIds)// .describedAs("Failed to pick up DefaultPackageTestCase via classpath scanning")// - .contains(uniqueIdForClass(ReflectionUtils.tryToLoadClass("DefaultPackageTestCase").get())); + .contains(uniqueIdForClass(ReflectionSupport.tryToLoadClass("DefaultPackageTestCase").get())); assertThat(uniqueIds).contains(uniqueIdForClass(Class1WithTestCases.class)); assertThat(uniqueIds).contains(uniqueIdForMethod(Class1WithTestCases.class, "test1()")); assertThat(uniqueIds).contains(uniqueIdForClass(Class2WithTestCases.class)); @@ -759,7 +758,7 @@ private TestDescriptor descriptorByUniqueId(UniqueId uniqueId) { } private List uniqueIds() { - return engineDescriptor.getDescendants().stream().map(TestDescriptor::getUniqueId).collect(toList()); + return engineDescriptor.getDescendants().stream().map(TestDescriptor::getUniqueId).toList(); } private LauncherDiscoveryRequestBuilder request() { @@ -835,6 +834,7 @@ void test4() { class HerTestClass extends MyTestClass { + @SuppressWarnings("JUnitMalformedDeclaration") @Test void test7(String param) { } @@ -842,6 +842,7 @@ void test7(String param) { class OtherTestClass { + @SuppressWarnings("JUnitMalformedDeclaration") static class NestedTestClass { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java index 87bbafe2485e..7d5a91951bc3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/DiscoveryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -170,6 +170,7 @@ void abstractTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class LocalTestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java index dfc305708a55..d371fd3f2e03 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsInnerClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java similarity index 88% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java index 434a105131fc..609785f4a3e4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsNestedTestClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -45,10 +45,12 @@ private static class NestedClassesTestCase { class InnerClass { } + @SuppressWarnings("JUnitMalformedDeclaration") @Nested static class StaticNestedClass { } + @SuppressWarnings("JUnitMalformedDeclaration") @Nested private class PrivateInnerClass { } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java index 8b2a36d9bf1f..47c9c7143815 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsPotentialTestContainerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java index d3bea9a00710..c07e5eff03c1 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestClassWithTestsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -95,6 +95,7 @@ void recursiveHierarchies() { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") private class PrivateClassWithTestMethod { @Test @@ -138,6 +139,7 @@ void second() { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class StaticTestCase { @Test @@ -145,6 +147,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") private static class PrivateStaticTestCase { @Test diff --git a/junit-jupiter-engine/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 similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestFactoryMethodTests.java index d0d390b7576d..6487d0927db3 100644 --- a/junit-jupiter-engine/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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,7 +21,7 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * Unit tests for {@link IsTestFactoryMethod}. @@ -57,7 +57,7 @@ void bogusFactoryMethodReturningCollectionOfStrings() { } private static Method method(String name) { - return ReflectionUtils.findMethod(ClassWithTestFactoryMethods.class, name).get(); + return ReflectionSupport.findMethod(ClassWithTestFactoryMethods.class, name).get(); } private static class ClassWithTestFactoryMethods { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java similarity index 80% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java index ae1eaa1cc33b..c8787aafaa20 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,7 +18,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ModifierSupport; +import org.junit.platform.commons.support.ReflectionSupport; /** * Unit tests for {@link IsTestMethod}. @@ -33,7 +34,7 @@ class IsTestMethodTests { void publicTestMethod() { Method method = method("publicTestMethod"); // Ensure that somebody doesn't accidentally delete the public modifier again. - assertTrue(ReflectionUtils.isPublic(method)); + assertTrue(ModifierSupport.isPublic(method)); assertThat(isTestMethod).accepts(method); } @@ -41,7 +42,7 @@ void publicTestMethod() { void publicTestMethodWithArgument() { Method method = method("publicTestMethodWithArgument", TestInfo.class); // Ensure that somebody doesn't accidentally delete the public modifier again. - assertTrue(ReflectionUtils.isPublic(method)); + assertTrue(ModifierSupport.isPublic(method)); assertThat(isTestMethod).accepts(method); } @@ -86,11 +87,11 @@ void bogusTestMethodReturningPrimitive() { } private static Method method(String name, Class... parameterTypes) { - return ReflectionUtils.findMethod(ClassWithTestMethods.class, name, parameterTypes).get(); + return ReflectionSupport.findMethod(ClassWithTestMethods.class, name, parameterTypes).get(); } private Method abstractMethod(String name) { - return ReflectionUtils.findMethod(AbstractClassWithAbstractTestMethod.class, name).get(); + return ReflectionSupport.findMethod(AbstractClassWithAbstractTestMethod.class, name).get(); } private static abstract class AbstractClassWithAbstractTestMethod { @@ -100,26 +101,32 @@ private static abstract class AbstractClassWithAbstractTestMethod { } + @SuppressWarnings("JUnitMalformedDeclaration") private static class ClassWithTestMethods { + @SuppressWarnings("JUnitMalformedDeclaration") @Test static void bogusStaticTestMethod() { } + @SuppressWarnings("JUnitMalformedDeclaration") @Test private void bogusPrivateTestMethod() { } + @SuppressWarnings("JUnitMalformedDeclaration") @Test String bogusTestMethodReturningObject() { return ""; } + @SuppressWarnings("JUnitMalformedDeclaration") @Test Void bogusTestMethodReturningVoidReference() { return null; } + @SuppressWarnings("JUnitMalformedDeclaration") @Test int bogusTestMethodReturningPrimitive() { return 0; diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java similarity index 85% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java index 3b2a8c783266..a71da9ec39b0 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/discovery/predicates/IsTestTemplateMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,7 +16,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestTemplate; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * Unit tests for {@link IsTestTemplateMethod}. @@ -38,7 +38,7 @@ void bogusTestTemplateMethodReturningObject() { } private static Method method(String name) { - return ReflectionUtils.findMethod(ClassWithTestTemplateMethods.class, name).get(); + return ReflectionSupport.findMethod(ClassWithTestTemplateMethods.class, name).get(); } private static class ClassWithTestTemplateMethods { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java index e30d5a20ebb0..c054f3ec1220 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/AbstractExecutableInvokerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -28,6 +28,7 @@ import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** @@ -82,7 +83,8 @@ private void thereIsAParameterResolverThatResolvesTheParameterTo(Object argument } private void testMethodWithASingleStringParameter() { - this.method = ReflectionUtils.findMethod(this.instance.getClass(), "singleStringParameter", String.class).get(); + this.method = ReflectionSupport.findMethod(this.instance.getClass(), "singleStringParameter", + String.class).get(); } private void register(ParameterResolver... resolvers) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java index b09c9cec7f83..c9db82e0dd9c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultExecutableInvokerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java index 24c836c173ee..8928a59a0607 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DefaultTestInstancesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java index 561ead14e647..f9f666cbda76 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/DynamicTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java index 305f94173a52..199b6903f46b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreConcurrencyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java index 07ba68b15886..2c62a186b9fa 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ExtensionContextStoreTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java similarity index 90% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java index 4ce912a74e73..58d96e0ecce1 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/InterceptingExecutableInvokerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -31,8 +31,8 @@ void invokeMethod() { @Override T invokeConstructor(Constructor constructor, Object outerInstance) { - return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), extensionContext, extensionRegistry, - passthroughInterceptor()); + return newInvoker().invoke(constructor, Optional.ofNullable(outerInstance), __ -> extensionContext, + extensionRegistry, passthroughInterceptor()); } private InterceptingExecutableInvoker newInvoker() { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java index 0a2f8abe0270..80eeca958fbd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/JupiterEngineExecutionContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -98,7 +98,7 @@ void closeAttemptExceptionWillBeThrownDownTheCallStack() throws Exception { assertThat(actualException) // .hasMessage("Failed to close extension context") // - .hasCauseReference(expectedCause); + .cause().isSameAs(expectedCause); } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java index 895aed7a1618..54e175b8bc6f 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/ParameterResolutionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,6 +32,7 @@ import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.extension.MutableExtensionRegistry; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** @@ -68,7 +69,7 @@ void resolveNestedConstructorArguments() { register(new NumberParameterResolver()); Class outerClass = ConstructorInjectionTestCase.class; - ConstructorInjectionTestCase outer = ReflectionUtils.newInstance(outerClass, "str"); + ConstructorInjectionTestCase outer = ReflectionSupport.newInstance(outerClass, "str"); Class innerClass = ConstructorInjectionTestCase.NestedTestCase.class; Object[] arguments = resolveConstructorParameters(innerClass, outer); @@ -313,7 +314,7 @@ private void testMethodWithASinglePrimitiveIntParameter() { } private void testMethodWith(String methodName, Class... parameterTypes) { - this.method = ReflectionUtils.findMethod(this.instance.getClass(), methodName, parameterTypes).get(); + this.method = ReflectionSupport.findMethod(this.instance.getClass(), methodName, parameterTypes).get(); } private void register(ParameterResolver... resolvers) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java index bae7f2e80be4..71035953ee13 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/UniqueIdParsingForArrayParameterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -55,6 +55,7 @@ void executeTestsForPrimitiveArrayMethodInjectionCases() { assertThat(UniqueId.parse(uniqueId.toString())).isEqualTo(uniqueId); } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(PrimitiveArrayParameterResolver.class) static class PrimitiveArrayMethodInjectionTestCase { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java similarity index 91% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java index 8fa982dc3a5e..9e04410e1c6f 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotation.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java similarity index 84% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java index 47dfdb80d95b..eec47744f5ca 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomAnnotationParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,7 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolver; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.0 @@ -29,7 +29,7 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - return ReflectionUtils.newInstance(parameterContext.getParameter().getType()); + return ReflectionSupport.newInstance(parameterContext.getParameter().getType()); } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java similarity index 90% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java index d1dc6f4983b3..5545582f7ce0 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomType.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java index 9a75754d2180..126e5ee751bd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/CustomTypeParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java index 1659da6705a3..ab714e19f011 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/DoubleParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java index a59dc6f7a898..b668e8fc9609 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/LongParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java index b8a0172970e7..5858080ad462 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfListsTypeBasedParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java index 3d7d531e4b77..e669fe26b07b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/MapOfStringsParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java index 7a73b82d8623..2419640c9fb4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NullIntegerParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java index 66d8e4d7e85f..d2450ec7cbf6 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/NumberParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java index d3fac903d770..23a693608381 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveArrayParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java index e042489a471e..0e986fffdaf7 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/execution/injection/sample/PrimitiveIntegerParameterResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java index 5c66a2ac0842..c1fc0ed4cfa1 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/AutoCloseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -109,6 +109,7 @@ void noShutdownMethod() { */ @Test void spyPermitsOnlyASingleAction() { + @SuppressWarnings("resource") AutoCloseSpy spy = new AutoCloseSpy("preconditions"); spy.close(); @@ -457,6 +458,7 @@ static class CloseMethodMustBeInvokedViaInterfaceTestCase implements TestInterfa } @TestInstance(PER_METHOD) + @SuppressWarnings("JUnitMalformedDeclaration") static class InstancePerMethodTestCase { @AutoClose @@ -489,6 +491,7 @@ void test2() { } @TestInstance(PER_CLASS) + @SuppressWarnings("JUnitMalformedDeclaration") static class InstancePerClassTestCase { static boolean closed = false; @@ -569,6 +572,7 @@ static void setup() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class SuperTestCase { @AutoClose @@ -589,6 +593,7 @@ void superTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class SubTestCase extends SuperTestCase { @AutoClose @@ -607,6 +612,7 @@ void subTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FailingFieldsTestCase { @AutoClose @@ -639,6 +645,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FailingFieldsEnclosingTestCase { @AutoClose diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java index 34c178c045e8..34ebef190656 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterAllTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -187,6 +187,7 @@ private void assertBeforeAllAndAfterAllCallbacks(Class testClass, int testsSt // ------------------------------------------------------------------------- // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooClassLevelCallbacks.class, BarClassLevelCallbacks.class }) static class TopLevelTestCase { @@ -207,6 +208,7 @@ void test() { } // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BazClassLevelCallbacks.class) static class SecondLevelTestCase extends TopLevelTestCase { @@ -227,6 +229,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(QuuxClassLevelCallbacks.class) static class ThirdLevelTestCase extends SecondLevelTestCase { @@ -247,6 +250,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(QuuxClassLevelCallbacks.class) static class ThirdLevelStaticHidingTestCase extends SecondLevelTestCase { @@ -287,6 +291,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooClassLevelCallbacks.class) static class ExceptionInBeforeAllMethodTestCase { @@ -307,6 +312,7 @@ static void afterAll() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooClassLevelCallbacks.class, ExceptionThrowingBeforeAllCallback.class }) static class ExceptionInBeforeAllCallbackTestCase { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java index 8c7f59f7eda3..e6856ea36e0e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterEachTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -288,6 +288,7 @@ void testMethodThrowsAnException() { static class ParentTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BarMethodLevelCallbacks.class) static class ChildTestCase extends ParentTestCase { @@ -306,6 +307,7 @@ default void defaultTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BarMethodLevelCallbacks.class) static class TestInterfaceTestCase implements TestInterface { @@ -315,6 +317,7 @@ void localTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooMethodLevelCallbacks.class, BarMethodLevelCallbacks.class }) static class OuterTestCase { @@ -354,6 +357,7 @@ void afterEachInnerMethod() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingBeforeEachCallback.class, BarMethodLevelCallbacks.class }) static class ExceptionInBeforeEachCallbackTestCase { @@ -374,6 +378,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooMethodLevelCallbacks.class, ExceptionThrowingAfterEachCallback.class, BarMethodLevelCallbacks.class }) static class ExceptionInAfterEachCallbackTestCase { @@ -394,6 +399,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooMethodLevelCallbacks.class) static class ExceptionInBeforeEachMethodTestCase { @@ -421,6 +427,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooMethodLevelCallbacks.class) static class ExceptionInAfterEachMethodTestCase { @@ -441,6 +448,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooMethodLevelCallbacks.class) static class ExceptionInTestMethodTestCase { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java index 89672560abba..119b2ecb6909 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/BeforeAndAfterTestExecutionCallbackTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -256,6 +256,7 @@ void testMethodThrowsAnException() { static class ParentTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BarTestExecutionCallbacks.class) static class ChildTestCase extends ParentTestCase { @@ -274,6 +275,7 @@ default void defaultTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BarTestExecutionCallbacks.class) static class TestInterfaceTestCase implements TestInterface { @@ -283,6 +285,7 @@ void localTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooTestExecutionCallbacks.class, BarTestExecutionCallbacks.class }) static class OuterTestCase { @@ -322,6 +325,7 @@ void afterInnerMethod() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingBeforeTestExecutionCallback.class, BarTestExecutionCallbacks.class }) static class ExceptionInBeforeTestExecutionCallbackTestCase { @@ -342,6 +346,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooTestExecutionCallbacks.class, ExceptionThrowingAfterTestExecutionCallback.class, BarTestExecutionCallbacks.class }) static class ExceptionInAfterTestExecutionCallbackTestCase { @@ -362,6 +367,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooTestExecutionCallbacks.class) static class ExceptionInBeforeEachMethodTestCase { @@ -382,6 +388,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooTestExecutionCallbacks.class) static class ExceptionInTestMethodTestCase { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java new file mode 100644 index 000000000000..2511a441a1bb --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/CloseablePathTests.java @@ -0,0 +1,411 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static com.google.common.jimfs.Configuration.unix; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.nio.file.Files.createDirectory; +import static java.nio.file.Files.createFile; +import static java.nio.file.Files.createSymbolicLink; +import static java.nio.file.Files.createTempDirectory; +import static java.nio.file.Files.delete; +import static java.nio.file.Files.deleteIfExists; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.condition.OS.WINDOWS; +import static org.junit.jupiter.api.io.CleanupMode.ALWAYS; +import static org.junit.jupiter.api.io.CleanupMode.DEFAULT; +import static org.junit.jupiter.api.io.CleanupMode.NEVER; +import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import com.google.common.jimfs.Jimfs; + +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.extension.AnnotatedElementContext; +import org.junit.jupiter.api.extension.ExtensionConfigurationException; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.io.TempDirFactory; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; + +/** + * Integration tests for the creation and cleanup of the {@link TempDirectory}. + * + * @since 5.9 + */ +@DisplayName("Temporary directory") +class CloseablePathTests extends AbstractJupiterTestEngineTests { + + private final AnnotatedElementContext elementContext = mock(); + private final ExtensionContext extensionContext = mock(); + + private TempDirectory.CloseablePath closeablePath; + + @Target(METHOD) + @Retention(RUNTIME) + @ValueSource(classes = { File.class, Path.class }) + private @interface ElementTypeSource { + } + + @BeforeEach + void setUpExtensionContext() { + var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), Namespace.GLOBAL); + when(extensionContext.getStore(any())).thenReturn(store); + } + + /** + * Integration tests for the creation of the {@link TempDirectory} based on the different result + * that {@link TempDirFactory#createTempDirectory(AnnotatedElementContext, ExtensionContext)} may provide. + * + * @since 5.11 + * @see TempDirFactory + */ + @Nested + @DisplayName("creation") + class Creation { + + private Path root; + + @BeforeEach + void setUpRootFolder() throws IOException { + root = createTempDirectory("root"); + } + + @AfterEach + void cleanupRoot() throws IOException { + delete(root); + } + + @DisplayName("succeeds if the factory returns a directory") + @ParameterizedTest + @ElementTypeSource + void factoryReturnsDirectoryDynamic(Class elementType) throws IOException { + TempDirFactory factory = (elementContext, extensionContext) -> createDirectory(root.resolve("directory")); + + closeablePath = TempDirectory.createTempDir(factory, DEFAULT, elementType, elementContext, + extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + delete(closeablePath.get()); + } + + @DisplayName("succeeds if the factory returns a symbolic link to a directory") + @ParameterizedTest + @ElementTypeSource + @DisabledOnOs(WINDOWS) + void factoryReturnsSymbolicLinkToDirectory(Class elementType) throws IOException { + Path directory = createDirectory(root.resolve("directory")); + TempDirFactory factory = (elementContext, + extensionContext) -> createSymbolicLink(root.resolve("symbolicLink"), directory); + + closeablePath = TempDirectory.createTempDir(factory, DEFAULT, elementType, elementContext, + extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + delete(closeablePath.get()); + delete(directory); + } + + @DisplayName("succeeds if the factory returns a directory on a non-default file system for a Path annotated element") + @Test + void factoryReturnsDirectoryOnNonDefaultFileSystemWithPath() throws IOException { + TempDirFactory factory = new JimfsFactory(); + + closeablePath = TempDirectory.createTempDir(factory, DEFAULT, Path.class, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + delete(closeablePath.get()); + } + + @DisplayName("fails if the factory returns null") + @ParameterizedTest + @ElementTypeSource + void factoryReturnsNull(Class elementType) throws IOException { + TempDirFactory factory = spy(new Factory(null)); + + assertThatExtensionConfigurationExceptionIsThrownBy( + () -> TempDirectory.createTempDir(factory, DEFAULT, elementType, elementContext, extensionContext)); + + verify(factory).close(); + } + + @DisplayName("fails if the factory returns a file") + @ParameterizedTest + @ElementTypeSource + void factoryReturnsFile(Class elementType) throws IOException { + Path file = createFile(root.resolve("file")); + TempDirFactory factory = spy(new Factory(file)); + + assertThatExtensionConfigurationExceptionIsThrownBy( + () -> TempDirectory.createTempDir(factory, DEFAULT, elementType, elementContext, extensionContext)); + + verify(factory).close(); + assertThat(file).doesNotExist(); + } + + @DisplayName("fails if the factory returns a symbolic link to a file") + @ParameterizedTest + @ElementTypeSource + @DisabledOnOs(WINDOWS) + void factoryReturnsSymbolicLinkToFile(Class elementType) throws IOException { + Path file = createFile(root.resolve("file")); + Path symbolicLink = createSymbolicLink(root.resolve("symbolicLink"), file); + TempDirFactory factory = spy(new Factory(symbolicLink)); + + assertThatExtensionConfigurationExceptionIsThrownBy( + () -> TempDirectory.createTempDir(factory, DEFAULT, elementType, elementContext, extensionContext)); + + verify(factory).close(); + assertThat(symbolicLink).doesNotExist(); + + delete(file); + } + + @DisplayName("fails if the factory returns a directory on a non-default file system for a File annotated element") + @Test + void factoryReturnsDirectoryOnNonDefaultFileSystemWithFile() throws IOException { + TempDirFactory factory = spy(new JimfsFactory()); + + assertThatExceptionOfType(ExtensionConfigurationException.class)// + .isThrownBy(() -> TempDirectory.createTempDir(factory, DEFAULT, File.class, elementContext, + extensionContext))// + .withMessage("Failed to create default temp directory")// + .withCauseInstanceOf(PreconditionViolationException.class)// + .havingCause().withMessage("temp directory with non-default file system cannot be injected into " + + File.class.getName() + " target"); + + verify(factory).close(); + } + + // Mockito spying a lambda fails with: VM does not support modification of given type + private record Factory(Path path) implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return path; + } + + } + + private static class JimfsFactory implements TempDirFactory { + + private final FileSystem fileSystem = Jimfs.newFileSystem(unix()); + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { + return createDirectory(fileSystem.getPath("/").resolve("directory")); + } + + @Override + public void close() throws IOException { + fileSystem.close(); + } + } + + private static void assertThatExtensionConfigurationExceptionIsThrownBy(ThrowingCallable callable) { + assertThatExceptionOfType(ExtensionConfigurationException.class)// + .isThrownBy(callable)// + .withMessage("Failed to create default temp directory")// + .withCauseInstanceOf(PreconditionViolationException.class)// + .havingCause().withMessage("temp directory must be a directory"); + } + + } + + /** + * Integration tests for cleanup of the {@link TempDirectory} when the {@link CleanupMode} is + * set to {@link CleanupMode#ALWAYS}, {@link CleanupMode#NEVER}, or {@link CleanupMode#ON_SUCCESS}. + * + * @since 5.9 + * @see TempDir + * @see CleanupMode + */ + @Nested + @DisplayName("cleanup") + class Cleanup { + + private final TempDirFactory factory = spy(TempDirFactory.Standard.INSTANCE); + + @AfterEach + void cleanupTempDirectory() throws IOException { + deleteIfExists(closeablePath.get()); + } + + @DisplayName("is done for a cleanup mode of ALWAYS") + @ParameterizedTest + @ElementTypeSource + void always(Class elementType, @TrackLogRecords LogRecordListener listener) throws IOException { + reset(factory); + + closeablePath = TempDirectory.createTempDir(factory, ALWAYS, elementType, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + + verify(factory).close(); + assertThat(closeablePath.get()).doesNotExist(); + assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// + .noneMatch(m -> m.startsWith("Skipping cleanup of temp dir")); + } + + @DisplayName("is not done for a cleanup mode of NEVER") + @ParameterizedTest + @ElementTypeSource + void never(Class elementType, @TrackLogRecords LogRecordListener listener) throws Exception { + reset(factory); + + when(elementContext.getAnnotatedElement()).thenReturn(TestCase.class.getDeclaredField("tempDir")); + + closeablePath = TempDirectory.createTempDir(factory, NEVER, elementType, elementContext, extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + + verify(factory).close(); + assertThat(closeablePath.get()).exists(); + assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// + .anyMatch(m -> m.startsWith("Skipping cleanup of temp dir ") + && m.endsWith(" for field TestCase.tempDir due to CleanupMode.NEVER.")); + } + + @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception (for annotated field)") + @ParameterizedTest + @ElementTypeSource + void onSuccessWithExceptionForAnnotatedField(Class elementType, @TrackLogRecords LogRecordListener listener) + throws Exception { + + Field field = TestCase.class.getDeclaredField("tempDir"); + + onSuccessWithException(elementType, listener, field, + " for field TestCase.tempDir due to CleanupMode.ON_SUCCESS."); + } + + @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception (for annotated method parameter)") + @ParameterizedTest + @ElementTypeSource + void onSuccessWithExceptionForAnnotatedMethodParameter(Class elementType, + @TrackLogRecords LogRecordListener listener) throws Exception { + + Method method = TestCase.class.getDeclaredMethod("test", TestInfo.class, Path.class); + Parameter parameter = method.getParameters()[1]; + + onSuccessWithException(elementType, listener, parameter, + "for parameter 'tempDir' in method test(TestInfo, Path) due to CleanupMode.ON_SUCCESS."); + } + + @DisplayName("is not done for a cleanup mode of ON_SUCCESS, if there is an exception (for annotated constructor parameter)") + @ParameterizedTest + @ElementTypeSource + void onSuccessWithExceptionForAnnotatedConstructorParameter(Class elementType, + @TrackLogRecords LogRecordListener listener) throws Exception { + + Constructor constructor = TestCase.class.getDeclaredConstructor(TestInfo.class, Path.class); + Parameter parameter = constructor.getParameters()[1]; + + onSuccessWithException(elementType, listener, parameter, + "for parameter 'tempDir' in constructor TestCase(TestInfo, Path) due to CleanupMode.ON_SUCCESS."); + } + + private void onSuccessWithException(Class elementType, @TrackLogRecords LogRecordListener listener, + AnnotatedElement annotatedElement, String expectedMessage) throws Exception { + + reset(factory); + + when(extensionContext.getExecutionException()).thenReturn(Optional.of(new Exception())); + when(elementContext.getAnnotatedElement()).thenReturn(annotatedElement); + + closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementType, elementContext, + extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + + verify(factory).close(); + assertThat(closeablePath.get()).exists(); + assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// + .anyMatch(m -> m.startsWith("Skipping cleanup of temp dir ") && m.endsWith(expectedMessage)); + } + + @DisplayName("is done for a cleanup mode of ON_SUCCESS, if there is no exception") + @ParameterizedTest + @ElementTypeSource + void onSuccessWithNoException(Class elementType, @TrackLogRecords LogRecordListener listener) + throws IOException { + + reset(factory); + + when(extensionContext.getExecutionException()).thenReturn(Optional.empty()); + + closeablePath = TempDirectory.createTempDir(factory, ON_SUCCESS, elementType, elementContext, + extensionContext); + assertThat(closeablePath.get()).isDirectory(); + + closeablePath.close(); + + verify(factory).close(); + assertThat(closeablePath.get()).doesNotExist(); + assertThat(listener.stream(Level.INFO)).map(LogRecord::getMessage)// + .noneMatch(m -> m.startsWith("Skipping cleanup of temp dir")); + } + + } + + static class TestCase { + + Path tempDir; + + TestCase(TestInfo testInfo, Path tempDir) { + } + + void test(TestInfo testInfo, Path tempDir) { + } + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ConfigLoaderExtension.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ConfigLoaderExtension.java new file mode 100644 index 000000000000..e5228e78624a --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ConfigLoaderExtension.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Demo extension for auto-detection of extensions loaded via Java's + * {@link java.util.ServiceLoader} mechanism. + * + * @since 5.11 + */ +public class ConfigLoaderExtension implements BeforeAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + } + +} diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java new file mode 100644 index 000000000000..d4eb9b5a3b25 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/DefaultTestReporterTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.commons.PreconditionViolationException; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoSettings; + +@MockitoSettings +public class DefaultTestReporterTests { + + @TempDir + Path tempDir; + + @Mock + ExtensionContext extensionContext; + + @Captor + ArgumentCaptor> actionCaptor; + + @InjectMocks + DefaultTestReporter testReporter; + + @Test + void copiesExistingFileToTarget() throws Throwable { + testReporter.publishFile(Files.writeString(tempDir.resolve("source"), "content"), MediaType.TEXT_PLAIN_UTF_8); + + verify(extensionContext).publishFile(eq("source"), eq(MediaType.TEXT_PLAIN_UTF_8), actionCaptor.capture()); + actionCaptor.getValue().accept(tempDir.resolve("target")); + + assertThat(tempDir.resolve("target")).usingCharset(UTF_8).hasContent("content"); + } + + @Test + void executesCustomActionWithTargetFile() throws Throwable { + testReporter.publishFile("target", MediaType.APPLICATION_OCTET_STREAM, + file -> Files.write(file, "content".getBytes())); + + verify(extensionContext).publishFile(eq("target"), eq(MediaType.APPLICATION_OCTET_STREAM), + actionCaptor.capture()); + actionCaptor.getValue().accept(tempDir.resolve("target")); + + assertThat(tempDir.resolve("target")).hasContent("content"); + } + + @Test + void copiesExistingDirectoryToTarget() throws Throwable { + var source = Files.createDirectory(tempDir.resolve("source")); + Files.writeString(source.resolve("source1"), "content1"); + var sourceSubDir = Files.createDirectory(source.resolve("subDir")); + Files.writeString(sourceSubDir.resolve("source2"), "content2"); + Files.writeString(Files.createDirectory(sourceSubDir.resolve("subSubDir")).resolve("source3"), "content3"); + + testReporter.publishDirectory(source); + + verify(extensionContext).publishDirectory(eq("source"), actionCaptor.capture()); + + var target = tempDir.resolve("target"); + actionCaptor.getValue().accept(target); + + assertThat(target).isDirectory(); + assertThat(target.resolve("source1")).usingCharset(UTF_8).hasContent("content1"); + var targetSubDir = target.resolve("subDir"); + assertThat(targetSubDir.resolve("source2")).usingCharset(UTF_8).hasContent("content2"); + assertThat(targetSubDir.resolve("subSubDir").resolve("source3")).usingCharset(UTF_8).hasContent("content3"); + } + + @Test + void executesCustomActionWithTargetDirectory() throws Throwable { + testReporter.publishDirectory("target", + dir -> Files.writeString(Files.createDirectory(dir).resolve("file"), "content", Charset.defaultCharset())); + + verify(extensionContext).publishDirectory(eq("target"), actionCaptor.capture()); + + var target = tempDir.resolve("target"); + actionCaptor.getValue().accept(target); + + assertThat(target.resolve("file")).hasContent("content"); + } + + @Test + void failsWhenPublishingMissingFile() { + var missingFile = tempDir.resolve("missingFile"); + + assertThatThrownBy(() -> testReporter.publishFile(missingFile, MediaType.APPLICATION_OCTET_STREAM)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("file must exist: " + missingFile); + } + + @Test + void failsWhenPublishingDirectoryAsFile() { + assertThatThrownBy(() -> testReporter.publishFile(tempDir, MediaType.APPLICATION_OCTET_STREAM)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("file must be a regular file: " + tempDir); + } + + @Test + void failsWhenPublishingMissingDirectory() { + var missingDir = tempDir.resolve("missingDir"); + + assertThatThrownBy(() -> testReporter.publishDirectory(missingDir)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("directory must exist: " + missingDir); + } + + @Test + void failsWhenPublishingFileAsDirectory() throws Exception { + var file = Files.createFile(tempDir.resolve("source")); + + assertThatThrownBy(() -> testReporter.publishDirectory(file)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("directory must be a directory: " + file); + } +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java similarity index 88% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java index 1db44a0f6198..96651e87078c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EnigmaException.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java index 67d6314d4b6b..57a14765bcc5 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/EventuallyInterruptibleInvocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java index b4c13b933d96..e414483a6bcc 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExecutionConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -151,6 +151,7 @@ private void assertExecutionConditionOverride(String deactivatePattern, int star // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @SystemProperty(key = FOO, value = BOGUS) @DeactivatedConditions static class TestCaseWithExecutionConditionOnClass { @@ -167,6 +168,7 @@ void atDisabledTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithExecutionConditionOnMethods { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java index 8d5694ce3071..c3214cf18aad 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionContextExecutionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -53,6 +53,7 @@ void twoTestClassesCanShareStateViaEngineExtensionContext() { assertThat(Parent.counter).hasValue(1); } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(OnlyIncrementCounterOnce.class) static class Parent { static final AtomicInteger counter = new AtomicInteger(0); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java similarity index 79% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java index 0d10f40a5fbd..c70238d7473e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistrationViaParametersAndFieldsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,12 +10,15 @@ package org.junit.jupiter.engine.extension; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotatedFields; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; +import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; @@ -44,6 +47,7 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; @@ -51,7 +55,6 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.Extension; import org.junit.jupiter.api.extension.ExtensionContext; @@ -59,15 +62,21 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestTemplateInvocationContext; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.api.fixtures.TrackLogRecords; +import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.config.JupiterConfiguration; import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.support.ModifierSupport; import org.junit.platform.commons.util.ExceptionUtils; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.testkit.engine.EngineExecutionResults; /** * Integration tests that verify support for extension registration via @@ -75,6 +84,7 @@ * * @since 5.8 */ +@SuppressWarnings("JUnitMalformedDeclaration") class ExtensionRegistrationViaParametersAndFieldsTests extends AbstractJupiterTestEngineTests { @Test @@ -122,6 +132,12 @@ void testTemplateMethodParameter() { assertTestsSucceeded(TestTemplateMethodParameterTestCase.class, 2); } + @Test + void multipleRegistrationsViaParameter(@TrackLogRecords LogRecordListener listener) { + assertOneTestSucceeded(MultipleRegistrationsViaParameterTestCase.class); + assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); + } + @Test void staticField() { assertOneTestSucceeded(StaticFieldTestCase.class); @@ -137,9 +153,11 @@ void fieldsWithTestInstancePerClass() { assertOneTestSucceeded(TestInstancePerClassFieldTestCase.class); } - @Test - void multipleRegistrationsViaField(@TrackLogRecords LogRecordListener listener) { - assertOneTestSucceeded(MultipleRegistrationsViaFieldTestCase.class); + @ParameterizedTest + @ValueSource(classes = { MultipleMixedRegistrationsViaFieldTestCase.class, + MultipleExtendWithRegistrationsViaFieldTestCase.class }) + void multipleRegistrationsViaField(Class testClass, @TrackLogRecords LogRecordListener listener) { + assertOneTestSucceeded(testClass); assertThat(getRegisteredLocalExtensions(listener)).containsExactly("LongParameterResolver", "DummyExtension"); } @@ -156,29 +174,11 @@ void duplicateRegistrationViaField() { finishedWithFailure(instanceOf(PreconditionViolationException.class), message(expectedMessage))); } - @Test - void registrationOrder(@TrackLogRecords LogRecordListener listener) { - assertOneTestSucceeded(AllInOneWithTestInstancePerMethodTestCase.class); - assertThat(getRegisteredLocalExtensions(listener))// - .containsExactly(// - "ClassLevelExtension2", // @RegisterExtension on static field - "StaticField2", // @ExtendWith on static field - "ClassLevelExtension1", // @RegisterExtension on static field - "StaticField1", // @ExtendWith on static field - "ConstructorParameter", // @ExtendWith on parameter in constructor - "BeforeAllParameter", // @ExtendWith on parameter in static @BeforeAll method - "BeforeEachParameter", // @ExtendWith on parameter in @BeforeEach method - "AfterEachParameter", // @ExtendWith on parameter in @AfterEach method - "AfterAllParameter", // @ExtendWith on parameter in static @AfterAll method - "TestParameter", // @ExtendWith on parameter in @Test method - "InstanceLevelExtension1", // @RegisterExtension on instance field - "InstanceField1", // @ExtendWith on instance field - "InstanceLevelExtension2", // @RegisterExtension on instance field - "InstanceField2" // @ExtendWith on instance field - ); - - listener.clear(); - assertOneTestSucceeded(AllInOneWithTestInstancePerClassTestCase.class); + @ParameterizedTest(name = "{0}") + @ValueSource(classes = { AllInOneWithTestInstancePerMethodTestCase.class, + AllInOneWithTestInstancePerClassTestCase.class }) + void registrationOrder(Class testClass, @TrackLogRecords LogRecordListener listener) { + assertOneTestSucceeded(testClass); assertThat(getRegisteredLocalExtensions(listener))// .containsExactly(// "ClassLevelExtension2", // @RegisterExtension on static field @@ -198,20 +198,38 @@ void registrationOrder(@TrackLogRecords LogRecordListener listener) { ); } + @Test + void registersProgrammaticTestInstancePostProcessors() { + assertOneTestSucceeded(ProgrammaticTestInstancePostProcessorTestCase.class); + } + + @Test + void createsExtensionPerInstance() { + var results = executeTests(request() // + .selectors(selectClass(InitializationPerInstanceTestCase.class)) // + .configurationParameter(JupiterConfiguration.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") // + ); + assertTestsSucceeded(results, 100); + } + private List getRegisteredLocalExtensions(LogRecordListener listener) { - // @formatter:off - return listener.stream(MutableExtensionRegistry.class, Level.FINER) - .map(LogRecord::getMessage) - .filter(message -> message.contains("local extension")) - .map(message -> { - message = message.replaceAll("from source .+", ""); - int indexOfDollarSign = message.indexOf("$"); - int indexOfAtSign = message.indexOf("@"); - int endIndex = (indexOfDollarSign > 1 ? indexOfDollarSign : indexOfAtSign); - return message.substring(message.lastIndexOf('.') + 1, endIndex); - }) - .collect(toList()); - // @formatter:on + return listener.stream(MutableExtensionRegistry.class, Level.FINER) // + .map(LogRecord::getMessage) // + .filter(message -> message.contains("local extension")) // + .map(message -> { + message = message.replaceAll(" from source .+", ""); + int beginIndex = message.lastIndexOf('.') + 1; + if (message.contains("late-init")) { + return message.substring(beginIndex, message.indexOf("]")); + } + else { + int indexOfDollarSign = message.indexOf("$"); + int indexOfAtSign = message.indexOf("@"); + int endIndex = (indexOfDollarSign > 1 ? indexOfDollarSign : indexOfAtSign); + return message.substring(beginIndex, endIndex); + } + }) // + .toList(); } private void assertOneTestSucceeded(Class testClass) { @@ -219,7 +237,11 @@ private void assertOneTestSucceeded(Class testClass) { } private void assertTestsSucceeded(Class testClass, int expected) { - executeTestsForClass(testClass).testEvents().assertStatistics( + assertTestsSucceeded(executeTestsForClass(testClass), expected); + } + + private static void assertTestsSucceeded(EngineExecutionResults results, int expected) { + results.testEvents().assertStatistics( stats -> stats.started(expected).succeeded(expected).skipped(0).aborted(0).failed(0)); } @@ -553,14 +575,37 @@ private static TestTemplateInvocationContext emptyTestTemplateInvocationContext( } } - static class MultipleRegistrationsViaFieldTestCase { + @ExtendWith(LongParameterResolver.class) + static class MultipleRegistrationsViaParameterTestCase { + + @Test + void test(@ExtendWith(DummyExtension.class) @ExtendWith(LongParameterResolver.class) Long number) { + assertThat(number).isEqualTo(42L); + } + } + + static class MultipleMixedRegistrationsViaFieldTestCase { @ExtendWith(LongParameterResolver.class) @RegisterExtension - Extension dummy = new DummyExtension(); + DummyExtension dummy = new DummyExtension(); @Test - void test() { + void test(Long number) { + assertThat(number).isEqualTo(42L); + } + } + + static class MultipleExtendWithRegistrationsViaFieldTestCase { + + @SuppressWarnings("unused") + @ExtendWith(LongParameterResolver.class) + @ExtendWith(DummyExtension.class) + Object field; + + @Test + void test(Long number) { + assertThat(number).isEqualTo(42L); } } @@ -580,6 +625,7 @@ void test() { */ static class StaticFieldTestCase { + @SuppressWarnings("unused") @MagicField private static String staticField1; @@ -612,8 +658,8 @@ static class InstanceFieldTestCase { @Test void test() { - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); + assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } } @@ -633,13 +679,13 @@ static class TestInstancePerClassFieldTestCase { @BeforeAll void beforeAll() { assertThat(staticField).isEqualTo("beforeAll - staticField"); - assertThat(instanceField).isNull(); + assertThat(instanceField).isEqualTo("postProcessTestInstance - instanceField"); } @Test void test() { assertThat(staticField).isEqualTo("beforeAll - staticField"); - assertThat(instanceField).isEqualTo("beforeEach - instanceField"); + assertThat(instanceField).isEqualTo("postProcessTestInstance - instanceField"); } } @@ -672,11 +718,11 @@ static class AllInOneWithTestInstancePerMethodTestCase { @RegisterExtension @Order(1) - private Extension instanceLevelExtension1 = new InstanceLevelExtension1(); + private InstanceLevelExtension1 instanceLevelExtension1 = new InstanceLevelExtension1(); @RegisterExtension @Order(3) - Extension instanceLevelExtension2 = new InstanceLevelExtension2(); + InstanceLevelExtension2 instanceLevelExtension2 = new InstanceLevelExtension2(); AllInOneWithTestInstancePerMethodTestCase(@ConstructorParameter String text) { assertThat(text).isEqualTo("enigma"); @@ -694,8 +740,8 @@ void beforeEach(@BeforeEachParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); + assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } @Test @@ -703,8 +749,8 @@ void test(@TestParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); + assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } @AfterEach @@ -712,8 +758,8 @@ void afterEach(@AfterEachParameter String text) { assertThat(text).isEqualTo("enigma"); assertThat(staticField1).isEqualTo("beforeAll - staticField1"); assertThat(staticField2).isEqualTo("beforeAll - staticField2"); - assertThat(instanceField1).isEqualTo("beforeEach - instanceField1"); - assertThat(instanceField2).isEqualTo("beforeEach - instanceField2"); + assertThat(instanceField1).isEqualTo("postProcessTestInstance - instanceField1"); + assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); } @AfterAll @@ -733,6 +779,55 @@ static class AllInOneWithTestInstancePerClassTestCase extends AllInOneWithTestIn } } + static class ProgrammaticTestInstancePostProcessorTestCase { + + @RegisterExtension + static Extension resolver = new InstanceField2.Extension(); + + @InstanceField2 + String instanceField2; + + @Test + void test() { + assertThat(instanceField2).isEqualTo("postProcessTestInstance - instanceField2"); + } + } + + @Execution(CONCURRENT) + static class InitializationPerInstanceTestCase { + @RegisterExtension + Extension extension = new InstanceParameterResolver<>(this); + + @Nested + class Wrapper { + + @RegisterExtension + Extension extension = new InstanceParameterResolver<>(this); + + @RepeatedTest(100) + void test(InitializationPerInstanceTestCase outerInstance, Wrapper innerInstance) { + assertSame(InitializationPerInstanceTestCase.this, outerInstance); + assertSame(Wrapper.this, innerInstance); + } + + } + + private record InstanceParameterResolver(T instance) implements ParameterResolver { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return instance.getClass().equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return instance; + } + } + } + } @Target(ElementType.PARAMETER) @@ -838,7 +933,7 @@ class Extension extends BaseParameterExtension { class DummyExtension implements Extension { } -class BaseFieldExtension implements BeforeAllCallback, BeforeEachCallback { +class BaseFieldExtension implements BeforeAllCallback, TestInstancePostProcessor { private final Class annotationType; @@ -849,14 +944,14 @@ class BaseFieldExtension implements BeforeAllCallback, Bef } @Override - public final void beforeAll(ExtensionContext context) throws Exception { - injectFields("beforeAll", context.getRequiredTestClass(), null, ReflectionUtils::isStatic); + public final void beforeAll(ExtensionContext context) { + injectFields("beforeAll", context.getRequiredTestClass(), null, ModifierSupport::isStatic); } @Override - public final void beforeEach(ExtensionContext context) throws Exception { - injectFields("beforeEach", context.getRequiredTestClass(), context.getRequiredTestInstance(), - ReflectionUtils::isNotStatic); + public final void postProcessTestInstance(Object testInstance, ExtensionContext context) { + injectFields("postProcessTestInstance", context.getRequiredTestClass(), testInstance, + ModifierSupport::isNotStatic); } private void injectFields(String trigger, Class testClass, Object instance, Predicate predicate) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java similarity index 65% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java index 180592fd09ec..ce27bf7d0c85 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ExtensionRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Stream; import org.junit.jupiter.api.Test; @@ -31,6 +32,7 @@ import org.junit.jupiter.api.extension.ParameterResolver; import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; import org.junit.jupiter.engine.config.JupiterConfiguration; +import org.junit.platform.commons.util.ClassNamePatternFilterUtils; /** * Tests for the {@link MutableExtensionRegistry}. @@ -39,7 +41,10 @@ */ class ExtensionRegistryTests { - private static final int NUM_DEFAULT_EXTENSIONS = 7; + private static final int NUM_CORE_EXTENSIONS = 7; + private static final int NUM_AUTO_REGISTERED_EXTENSIONS_IN_THIS_PROJECT = 1; // OpenTestReportGenerationSystemPropertyOverride + private static final int NUM_DEFAULT_EXTENSIONS = NUM_CORE_EXTENSIONS + + NUM_AUTO_REGISTERED_EXTENSIONS_IN_THIS_PROJECT; private final JupiterConfiguration configuration = mock(); @@ -49,7 +54,7 @@ class ExtensionRegistryTests { void newRegistryWithoutParentHasDefaultExtensions() { List extensions = registry.getExtensions(Extension.class); - assertEquals(NUM_DEFAULT_EXTENSIONS, extensions.size()); + assertEquals(NUM_CORE_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(); } @@ -57,17 +62,82 @@ void newRegistryWithoutParentHasDefaultExtensions() { void newRegistryWithoutParentHasDefaultExtensionsPlusAutodetectedExtensionsLoadedViaServiceLoader() { when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); + when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(__ -> true); + registry = createRegistryWithDefaultExtensions(configuration); + + List extensions = registry.getExtensions(Extension.class); + + assertEquals(NUM_DEFAULT_EXTENSIONS + 2, extensions.size()); + assertDefaultGlobalExtensionsAreRegistered(4); + + assertExtensionRegistered(registry, ServiceLoaderExtension.class); + assertEquals(4, countExtensions(registry, BeforeAllCallback.class)); + } + + @Test + void registryIncludesAndExcludesSpecificAutoDetectedExtensions() { + when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); + when(configuration.getFilterForAutoDetectedExtensions()).thenReturn( + extensionFilter(ServiceLoaderExtension.class.getName(), ConfigLoaderExtension.class.getName())); registry = createRegistryWithDefaultExtensions(configuration); List extensions = registry.getExtensions(Extension.class); - assertEquals(NUM_DEFAULT_EXTENSIONS + 1, extensions.size()); + assertEquals(NUM_DEFAULT_EXTENSIONS, extensions.size()); assertDefaultGlobalExtensionsAreRegistered(3); assertExtensionRegistered(registry, ServiceLoaderExtension.class); assertEquals(3, countExtensions(registry, BeforeAllCallback.class)); } + @Test + void registryIncludesAllAutoDetectedExtensionsAndExcludesNone() { + when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); + when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(extensionFilter("*", "")); + registry = createRegistryWithDefaultExtensions(configuration); + + List extensions = registry.getExtensions(Extension.class); + + assertEquals(NUM_DEFAULT_EXTENSIONS + 2, extensions.size()); + assertDefaultGlobalExtensionsAreRegistered(4); + + assertExtensionRegistered(registry, ServiceLoaderExtension.class); + assertExtensionRegistered(registry, ConfigLoaderExtension.class); + assertEquals(4, countExtensions(registry, BeforeAllCallback.class)); + } + + @Test + void registryIncludesSpecificAutoDetectedExtensionsAndExcludesAll() { + when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); + when(configuration.getFilterForAutoDetectedExtensions()).thenReturn( + extensionFilter(ServiceLoaderExtension.class.getName(), "*")); + registry = createRegistryWithDefaultExtensions(configuration); + + List extensions = registry.getExtensions(Extension.class); + + assertEquals(NUM_CORE_EXTENSIONS, extensions.size()); + assertDefaultGlobalExtensionsAreRegistered(2); + + assertExtensionNotRegistered(registry, ServiceLoaderExtension.class); + assertEquals(2, countExtensions(registry, BeforeAllCallback.class)); + } + + @Test + void registryIncludesAndExcludesSameAutoDetectedExtension() { + when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true); + when(configuration.getFilterForAutoDetectedExtensions()).thenReturn( + extensionFilter(ServiceLoaderExtension.class.getName(), ServiceLoaderExtension.class.getName())); + registry = createRegistryWithDefaultExtensions(configuration); + + List extensions = registry.getExtensions(Extension.class); + + assertEquals(NUM_CORE_EXTENSIONS, extensions.size()); + assertDefaultGlobalExtensionsAreRegistered(2); + + assertExtensionNotRegistered(registry, ServiceLoaderExtension.class); + assertEquals(2, countExtensions(registry, BeforeAllCallback.class)); + } + @Test void registerExtensionByImplementingClass() { registry.registerExtension(MyExtension.class); @@ -153,6 +223,11 @@ private void assertExtensionRegistered(ExtensionRegistry registry, Class extensionType.getSimpleName() + " should be present"); } + private void assertExtensionNotRegistered(ExtensionRegistry registry, Class extensionType) { + assertTrue(registry.getExtensions(extensionType).isEmpty(), + () -> extensionType.getSimpleName() + " should not be present"); + } + private void assertDefaultGlobalExtensionsAreRegistered() { assertDefaultGlobalExtensionsAreRegistered(2); } @@ -173,6 +248,12 @@ private void assertDefaultGlobalExtensionsAreRegistered(long bacCount) { assertEquals(1, countExtensions(registry, InvocationInterceptor.class)); } + private static Predicate> extensionFilter(String includes, String excludes) { + var nameFilter = ClassNamePatternFilterUtils.includeMatchingClassNames(includes) // + .and(ClassNamePatternFilterUtils.excludeMatchingClassNames(excludes)); + return clazz -> nameFilter.test(clazz.getName()); + } + // ------------------------------------------------------------------------- interface MyExtensionApi extends Extension { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java similarity index 76% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java index 1128a4c6ad6e..b9b39cecff58 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/InvocationInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,8 @@ package org.junit.jupiter.engine.extension; +import static java.util.function.Function.identity; +import static java.util.function.Predicate.isEqual; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -23,7 +25,9 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.EnumSet; +import java.util.Collection; +import java.util.List; +import java.util.function.UnaryOperator; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; @@ -31,9 +35,12 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.DynamicTestInvocationContext; import org.junit.jupiter.api.extension.ExtendWith; @@ -59,6 +66,7 @@ void failsTestWhenInterceptorChainDoesNotCallInvocation() { message(it -> it.startsWith("Chain of InvocationInterceptors never called invocation"))))); } + @SuppressWarnings("JUnitMalformedDeclaration") static class InvocationIgnoringInterceptorTestCase { @RegisterExtension Extension interceptor = new InvocationInterceptor() { @@ -82,6 +90,7 @@ void successTestWhenInterceptorChainSkippedInvocation() { results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(1)); } + @SuppressWarnings("JUnitMalformedDeclaration") static class InvocationSkippedTestCase { @RegisterExtension Extension interceptor = new InvocationInterceptor() { @@ -108,6 +117,7 @@ void failsTestWhenInterceptorChainCallsInvocationMoreThanOnce() { "Chain of InvocationInterceptors called invocation multiple times instead of just once"))))); } + @SuppressWarnings("JUnitMalformedDeclaration") static class DoubleInvocationInterceptorTestCase { @RegisterExtension Extension interceptor = new InvocationInterceptor() { @@ -131,29 +141,78 @@ Stream callsInterceptors() { var results = executeTestsForClass(TestCaseWithThreeInterceptors.class); results.testEvents().assertStatistics(stats -> stats.failed(0).succeeded(3)); + return Arrays.stream(InvocationType.values()) // - .map(invocationType -> dynamicTest(invocationType.name(), () -> { - assertThat(getEvents(results, EnumSet.of(invocationType)).distinct()) // - .containsExactly("before:foo", "before:bar", "before:baz", "test", "after:baz", "after:bar", - "after:foo"); - })); + .map(it -> dynamicTest(it.name(), () -> verifyEvents(results, it))); + } + + private void verifyEvents(EngineExecutionResults results, InvocationType invocationType) { + var beforeEvents = List.of("before:foo", "before:bar", "before:baz"); + var testEvent = List.of("test"); + var afterEvents = List.of("after:baz", "after:bar", "after:foo"); + var allEvents = Stream.of(beforeEvents, testEvent, afterEvents).flatMap(Collection::stream).toList(); + String testClassName = TestCaseWithThreeInterceptors.class.getName(); + + var expectedElements = switch (invocationType) { + case BEFORE_ALL, AFTER_ALL -> prefixed(allEvents, testClassName); + case CONSTRUCTOR -> concatStreams( + prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "test(TestReporter)"), + prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "testTemplate(TestReporter)[1]"), + prefixed(allEvents, it -> it.endsWith(":bar") ? testClassName : "testFactory(TestReporter)")); + case BEFORE_EACH, AFTER_EACH -> concatStreams(prefixed(allEvents, "test(TestReporter)"), + prefixed(allEvents, "testTemplate(TestReporter)[1]"), prefixed(allEvents, "testFactory(TestReporter)")); + case TEST_METHOD -> prefixed(allEvents, "test(TestReporter)"); + case TEST_TEMPLATE_METHOD -> prefixed(allEvents, "testTemplate(TestReporter)[1]"); + case TEST_FACTORY_METHOD -> prefixed(allEvents, "testFactory(TestReporter)"); + case DYNAMIC_TEST -> concatStreams(prefixed(beforeEvents, "testFactory(TestReporter)[1]"), + prefixed(testEvent, "testFactory(TestReporter)"), + prefixed(afterEvents, "testFactory(TestReporter)[1]")); + }; + + assertThat(getEvents(results, invocationType)) // + .containsExactlyElementsOf(expectedElements.toList()); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private static Stream concatStreams(Stream... items) { + return Stream.of(items).flatMap(identity()); + } + + private static Stream prefixed(List values, String prefix) { + return prefixed(values, __ -> prefix); } - private Stream getEvents(EngineExecutionResults results, EnumSet types) { - return results.allEvents().reportingEntryPublished() // - .map(event -> event.getPayload(ReportEntry.class).orElseThrow()) // - .map(ReportEntry::getKeyValuePairs) // - .filter(map -> map.keySet().stream().map(InvocationType::valueOf).anyMatch(types::contains)) // - .flatMap(map -> map.values().stream()); + private static Stream prefixed(List values, UnaryOperator prefixGenerator) { + return values.stream() // + .map(it -> "[%s] %s".formatted(prefixGenerator.apply(it), it)); } + private Stream getEvents(EngineExecutionResults results, InvocationType invocationType) { + return results.allEvents().reportingEntryPublished().stream() // + .flatMap(event -> { + var reportEntry = event.getPayload(ReportEntry.class).orElseThrow(); + var keyValuePairs = reportEntry.getKeyValuePairs(); + if (keyValuePairs.keySet().stream() // + .map(InvocationType::valueOf) // + .anyMatch(isEqual(invocationType))) { + return keyValuePairs.values().stream() // + .map(it -> "[%s] %s".formatted(event.getTestDescriptor().getLegacyReportingName(), it)); + } + return Stream.empty(); + }); + } + + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooInvocationInterceptor.class, BarInvocationInterceptor.class, BazInvocationInterceptor.class }) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class TestCaseWithThreeInterceptors { public TestCaseWithThreeInterceptors(TestReporter reporter) { publish(reporter, InvocationType.CONSTRUCTOR); } + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeAll static void beforeAll(TestReporter reporter) { publish(reporter, InvocationType.BEFORE_ALL); @@ -164,16 +223,19 @@ void beforeEach(TestReporter reporter) { publish(reporter, InvocationType.BEFORE_EACH); } + @Order(1) @Test void test(TestReporter reporter) { publish(reporter, InvocationType.TEST_METHOD); } + @Order(2) @RepeatedTest(1) void testTemplate(TestReporter reporter) { publish(reporter, InvocationType.TEST_TEMPLATE_METHOD); } + @Order(3) @TestFactory DynamicTest testFactory(TestReporter reporter) { publish(reporter, InvocationType.TEST_FACTORY_METHOD); @@ -187,6 +249,7 @@ void afterEach(TestReporter reporter) { publish(reporter, InvocationType.AFTER_EACH); } + @SuppressWarnings("JUnitMalformedDeclaration") @AfterAll static void afterAll(TestReporter reporter) { publish(reporter, InvocationType.AFTER_ALL); @@ -218,6 +281,11 @@ abstract static class ReportingInvocationInterceptor implements InvocationInterc this.name = name; } + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + @Override public void interceptBeforeAllMethod(Invocation invocation, ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) @@ -293,8 +361,8 @@ public void interceptDynamicTest(Invocation invocation, DynamicTestInvocat assertThat(invocationContext.getExecutable()).isNotNull(); assertThat(extensionContext.getUniqueId()).isNotBlank(); assertThat(extensionContext.getElement()).isEmpty(); - assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)).contains( - testClass.getDeclaredMethod("testFactory", TestReporter.class)); + assertThat(extensionContext.getParent().flatMap(ExtensionContext::getTestMethod)) // + .contains(testClass.getDeclaredMethod("testFactory", TestReporter.class)); reportAndProceed(invocation, extensionContext, InvocationType.DYNAMIC_TEST); } @@ -344,6 +412,12 @@ static class BarInvocationInterceptor extends ReportingInvocationInterceptor { BarInvocationInterceptor() { super("bar"); } + + @SuppressWarnings("deprecation") + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.DEFAULT; + } } static class BazInvocationInterceptor extends ReportingInvocationInterceptor { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java index b204cc241f45..30f8cb052e21 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/LifecycleMethodExecutionExceptionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -297,6 +297,7 @@ private boolean executeThrowingOutOfMemoryException() { // ------------------------------------------ + @SuppressWarnings("JUnitMalformedDeclaration") static class BaseTestCase { @BeforeAll static void throwBeforeAll() { @@ -348,6 +349,7 @@ static class SwallowingTestCase extends BaseTestCase { static class UnrecoverableExceptionTestCase extends BaseTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ShouldNotBeCalledHandler.class) @ExtendWith(SwallowExceptionHandler.class) @ExtendWith(RethrowExceptionHandler.class) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java index 19d28c9fcbb9..ff8560830382 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedClassTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java index 6291a7b4ecfa..2d859001066a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -337,6 +337,7 @@ private Events executeRandomTestCaseInParallelWithRandomSeed(String seed) { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class BaseTestCase { @Test @@ -349,7 +350,7 @@ void c() { } - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "JUnitMalformedDeclaration" }) @TestMethodOrder(MethodName.class) static class MethodNameTestCase extends BaseTestCase { @@ -402,7 +403,7 @@ void zzz() { } } - @SuppressWarnings({ "deprecation", "unused" }) + @SuppressWarnings({ "deprecation", "unused", "JUnitMalformedDeclaration" }) @TestMethodOrder(org.junit.jupiter.api.MethodOrderer.Alphanumeric.class) static class AlphanumericTestCase extends BaseTestCase { @@ -455,6 +456,7 @@ void zzz() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(MethodOrderer.DisplayName.class) static class DisplayNameTestCase { @@ -527,6 +529,7 @@ void No_display_name_attribute_2_caps() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(OrderAnnotation.class) static class OrderAnnotationTestCase { @@ -594,6 +597,7 @@ class NestedOrderAnnotationTestCase extends OrderAnnotationTestCase { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(Random.class) static class RandomTestCase { @@ -626,6 +630,7 @@ void test5() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(MisbehavingByAdding.class) static class MisbehavingByAddingTestCase { @@ -643,6 +648,7 @@ void test2() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(MisbehavingByRemoving.class) static class MisbehavingByRemovingTestCase { @@ -712,6 +718,7 @@ public void orderMethods(MethodOrdererContext context) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class WithoutTestMethodOrderTestCase { @BeforeEach diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java index 126f662d47ef..1cd4632dc28e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/OrderedProgrammaticExtensionRegistrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -200,6 +200,7 @@ private void assertOutcome(Class testClass, String... values) { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") private static class AbstractTestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java index 5aa8f1205d64..500a1fcbd1f3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ParameterResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -243,6 +243,7 @@ private void assertEventsForParameterizedTypes(EngineExecutionResults executionR // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(CustomTypeParameterResolver.class) static class ConstructorInjectionTestCase { @@ -281,6 +282,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(CustomAnnotationParameterResolver.class) static class AnnotatedParameterConstructorInjectionTestCase { @@ -320,6 +322,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ CustomTypeParameterResolver.class, CustomAnnotationParameterResolver.class }) static class MethodInjectionTestCase { @@ -362,6 +365,7 @@ void overloadedName(CustomType customType, @CustomAnnotation String value) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(NullIntegerParameterResolver.class) static class NullMethodInjectionTestCase { @@ -376,6 +380,7 @@ void injectPrimitive(int number) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(PrimitiveIntegerParameterResolver.class) static class PrimitiveIntegerMethodInjectionTestCase { @@ -385,6 +390,7 @@ void intPrimitive(int i) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(PrimitiveArrayParameterResolver.class) static class PrimitiveArrayMethodInjectionTestCase { @@ -394,6 +400,7 @@ void primitiveArray(int... ints) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(NumberParameterResolver.class) static class PotentiallyIncompatibleTypeMethodInjectionTestCase { @@ -417,6 +424,7 @@ void doubleParameterInjection(Double number) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class BeforeAndAfterMethodInjectionTestCase { @BeforeEach @@ -435,6 +443,7 @@ void after(TestInfo testInfo) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("custom class name") static class BeforeAndAfterAllMethodInjectionTestCase { @@ -453,6 +462,7 @@ static void afterAll(TestInfo testInfo) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class ExtendWithOnMethodTestCase { /** @@ -463,6 +473,7 @@ static class ExtendWithOnMethodTestCase { * * @see #523 */ + @SuppressWarnings("JUnitMalformedDeclaration") @BeforeEach @AfterEach void setUpAndTearDown(CustomType customType, @CustomAnnotation String value) { @@ -479,6 +490,7 @@ void testMethodWithExtensionAnnotation(CustomType customType, @CustomAnnotation } } + @SuppressWarnings("JUnitMalformedDeclaration") static class ParameterizedTypeTestCase { @Test @@ -489,6 +501,7 @@ void testMapOfStrings(Map map) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TypeBasedParameterResolverTestCase { @Test @ExtendWith(MapOfListsTypeBasedParameterResolver.class) diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/PreInterruptCallbackTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/PreInterruptCallbackTests.java new file mode 100644 index 000000000000..13ea7eff61d1 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/PreInterruptCallbackTests.java @@ -0,0 +1,245 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; +import static org.junit.jupiter.api.parallel.Resources.SYSTEM_OUT; +import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.PreInterruptCallback; +import org.junit.jupiter.api.extension.PreInterruptContext; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.Constants; +import org.junit.platform.testkit.engine.Events; + +/** + * @since 5.12 + */ +@Isolated +class PreInterruptCallbackTests extends AbstractJupiterTestEngineTests { + private static final String TC = "test"; + private static final String TIMEOUT_ERROR_MSG = TC + "() timed out after 1 microsecond"; + private static final String DEFAULT_ENABLE_PROPERTY = Constants.EXTENSIONS_TIMEOUT_THREAD_DUMP_ENABLED_PROPERTY_NAME; + private static final AtomicBoolean interruptedTest = new AtomicBoolean(); + private static final CompletableFuture testThreadExecutionDone = new CompletableFuture<>(); + private static final AtomicReference interruptedTestThread = new AtomicReference<>(); + private static final AtomicBoolean interruptCallbackShallThrowException = new AtomicBoolean(); + private static final AtomicReference calledPreInterruptContext = new AtomicReference<>(); + + @BeforeEach + void setUp() { + interruptedTest.set(false); + interruptCallbackShallThrowException.set(false); + calledPreInterruptContext.set(null); + } + + @AfterEach + void tearDown() { + calledPreInterruptContext.set(null); + interruptedTestThread.set(null); + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + @ResourceLock(value = SYSTEM_OUT, mode = READ_WRITE) + void testCaseWithDefaultInterruptCallbackEnabled() { + String orgValue = System.getProperty(DEFAULT_ENABLE_PROPERTY); + System.setProperty(DEFAULT_ENABLE_PROPERTY, Boolean.TRUE.toString()); + PrintStream orgOutStream = System.out; + Events tests; + String output; + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + PrintStream outStream = new PrintStream(buffer); + System.setOut(outStream); + tests = executeTestsForClass(DefaultPreInterruptCallbackTimeoutOnMethodTestCase.class).testEvents(); + output = buffer.toString(StandardCharsets.UTF_8); + } + finally { + System.setOut(orgOutStream); + if (orgValue != null) { + System.setProperty(DEFAULT_ENABLE_PROPERTY, orgValue); + } + else { + System.clearProperty(DEFAULT_ENABLE_PROPERTY); + } + } + + assertTestHasTimedOut(tests); + assertTrue(interruptedTest.get()); + Thread thread = Thread.currentThread(); + + assertThat(output) // + .containsSubsequence( + "Thread \"%s\" prio=%d Id=%d %s will be interrupted.".formatted(thread.getName(), + thread.getPriority(), thread.threadId(), Thread.State.TIMED_WAITING), // + "java.lang.Thread.sleep", // + "org.junit.jupiter.engine.extension.PreInterruptCallbackTests$DefaultPreInterruptCallbackTimeoutOnMethodTestCase.test(PreInterruptCallbackTests.java"); + + assertThat(output) // + .containsSubsequence( // + "junit-jupiter-timeout-watcher", // + "org.junit.jupiter.engine.extension.PreInterruptThreadDumpPrinter.beforeThreadInterrupt"); + } + + @Test + void testCaseWithNoInterruptCallbackEnabled() { + Events tests = executeTestsForClass(DefaultPreInterruptCallbackTimeoutOnMethodTestCase.class).testEvents(); + assertTestHasTimedOut(tests); + assertTrue(interruptedTest.get()); + } + + @Test + void testCaseWithDeclaredInterruptCallbackEnabled() { + Events tests = executeTestsForClass(DefaultPreInterruptCallbackWithExplicitCallbackTestCase.class).testEvents(); + assertTestHasTimedOut(tests); + assertTrue(interruptedTest.get()); + PreInterruptContext preInterruptContext = calledPreInterruptContext.get(); + assertNotNull(preInterruptContext); + assertNotNull(preInterruptContext.getThreadToInterrupt()); + assertEquals(preInterruptContext.getThreadToInterrupt(), interruptedTestThread.get()); + } + + @Test + void testCaseWithDeclaredInterruptCallbackEnabledWithSeparateThread() throws Exception { + Events tests = executeTestsForClass( + DefaultPreInterruptCallbackWithExplicitCallbackWithSeparateThreadTestCase.class).testEvents(); + assertOneFailedTest(tests); + tests.failed().assertEventsMatchExactly( + event(test(TC), finishedWithFailure(instanceOf(TimeoutException.class)))); + + //Wait until the real test thread was interrupted due to executor.shutdown(), otherwise the asserts below will be flaky. + testThreadExecutionDone.get(1, TimeUnit.SECONDS); + + assertTrue(interruptedTest.get()); + PreInterruptContext preInterruptContext = calledPreInterruptContext.get(); + assertNotNull(preInterruptContext); + assertNotNull(preInterruptContext.getThreadToInterrupt()); + assertEquals(preInterruptContext.getThreadToInterrupt(), interruptedTestThread.get()); + } + + @Test + void testCaseWithDeclaredInterruptCallbackThrowsException() { + interruptCallbackShallThrowException.set(true); + Events tests = executeTestsForClass(DefaultPreInterruptCallbackWithExplicitCallbackTestCase.class).testEvents(); + tests.failed().assertEventsMatchExactly(event(test(TC), + finishedWithFailure(instanceOf(TimeoutException.class), message(TIMEOUT_ERROR_MSG), + suppressed(0, instanceOf(InterruptedException.class)), + suppressed(1, instanceOf(IllegalStateException.class))))); + assertTrue(interruptedTest.get()); + PreInterruptContext preInterruptContext = calledPreInterruptContext.get(); + assertNotNull(preInterruptContext); + assertNotNull(preInterruptContext.getThreadToInterrupt()); + assertEquals(preInterruptContext.getThreadToInterrupt(), interruptedTestThread.get()); + } + + private static void assertTestHasTimedOut(Events tests) { + assertOneFailedTest(tests); + tests.failed().assertEventsMatchExactly( + event(test(TC), finishedWithFailure(instanceOf(TimeoutException.class), message(TIMEOUT_ERROR_MSG), // + suppressed(0, instanceOf(InterruptedException.class))// + ))); + } + + private static void assertOneFailedTest(Events tests) { + tests.assertStatistics(stats -> stats.started(1).succeeded(0).failed(1)); + } + + static class TestPreInterruptCallback implements PreInterruptCallback { + + @Override + public void beforeThreadInterrupt(PreInterruptContext preInterruptContext, ExtensionContext extensionContext) { + assertNotNull(extensionContext); + + calledPreInterruptContext.set(preInterruptContext); + if (interruptCallbackShallThrowException.get()) { + throw new IllegalStateException("Test-Ex"); + } + } + } + + static class DefaultPreInterruptCallbackTimeoutOnMethodTestCase { + @Test + @Timeout(value = 1, unit = TimeUnit.MICROSECONDS) + void test() throws InterruptedException { + try { + Thread.sleep(1000); + } + catch (InterruptedException ex) { + interruptedTest.set(true); + interruptedTestThread.set(Thread.currentThread()); + throw ex; + } + } + } + + @ExtendWith(TestPreInterruptCallback.class) + static class DefaultPreInterruptCallbackWithExplicitCallbackTestCase { + @Test + @Timeout(value = 1, unit = TimeUnit.MICROSECONDS) + void test() throws InterruptedException { + try { + Thread.sleep(1000); + } + catch (InterruptedException ex) { + interruptedTest.set(true); + interruptedTestThread.set(Thread.currentThread()); + throw ex; + } + } + } + + @ExtendWith(TestPreInterruptCallback.class) + static class DefaultPreInterruptCallbackWithExplicitCallbackWithSeparateThreadTestCase { + @Test + @Timeout(value = 200, unit = TimeUnit.MILLISECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void test() throws InterruptedException { + try { + Thread.sleep(2000); + } + catch (InterruptedException ex) { + interruptedTest.set(true); + interruptedTestThread.set(Thread.currentThread()); + throw ex; + } + finally { + testThreadExecutionDone.complete(null); + } + } + } +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java index 23e4cc9ec564..e168d58cc974 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ProgrammaticExtensionRegistrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,7 +16,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.junit.platform.commons.util.ReflectionUtils.makeAccessible; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields; +import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; @@ -47,7 +48,6 @@ import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.engine.execution.injection.sample.LongParameterResolver; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.AnnotationUtils; import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -299,6 +299,7 @@ private static void assertWisdom(CrystalBall crystalBall, String wisdom, String assertEquals("Outlook good", wisdom, useCase); } + @SuppressWarnings("JUnitMalformedDeclaration") static class InstanceLevelExtensionRegistrationTestCase { @RegisterExtension @@ -321,6 +322,7 @@ void afterEach(String wisdom) { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExtensionInjector.class) static class InstanceLevelExtensionRegistrationWithInjectedExtensionTestCase { @@ -344,6 +346,7 @@ void afterEach(String wisdom) { } + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(PER_CLASS) static class InstanceLevelExtensionRegistrationWithTestInstancePerClassLifecycleTestCase { @@ -377,6 +380,7 @@ void afterAll(String wisdom) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class ClassLevelExtensionRegistrationTestCase { @RegisterExtension @@ -409,8 +413,10 @@ static void afterAll(String wisdom) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class SubClassLevelExtensionRegistrationTestCase extends ClassLevelExtensionRegistrationTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @Test @Override void test(String wisdom) { @@ -446,8 +452,10 @@ static void afterAll(String wisdom) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class ExtensionRegistrationFromInterfaceTestCase implements ClassLevelExtensionRegistrationInterface { + @SuppressWarnings("JUnitMalformedDeclaration") @Test void test(String wisdom) { assertWisdom(crystalBall, wisdom, "@Test"); @@ -475,6 +483,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte } + @SuppressWarnings("JUnitMalformedDeclaration") static class ClassLevelExtensionRegistrationParentTestCase { @RegisterExtension @@ -500,6 +509,7 @@ static class ClassLevelExtensionRegistrationChildTestCase extends ClassLevelExte } + @SuppressWarnings("JUnitMalformedDeclaration") static class InstanceLevelExtensionRegistrationParentTestCase { @RegisterExtension @@ -564,8 +574,10 @@ public void beforeEach(ExtensionContext context) throws Exception { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class InstanceLevelCustomExtensionApiTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension CustomExtension extension = new CustomExtensionImpl(); @@ -576,8 +588,10 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class ClassLevelCustomExtensionApiTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension static CustomExtension extension = new CustomExtensionImpl(); @@ -588,6 +602,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AbstractTestCase { @Test @@ -598,6 +613,7 @@ void test() { static class InstanceLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension private Extension extension = new Extension() { }; @@ -606,6 +622,7 @@ static class InstanceLevelExtensionRegistrationWithPrivateFieldTestCase extends static class ClassLevelExtensionRegistrationWithPrivateFieldTestCase extends AbstractTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension private static Extension extension = new Extension() { }; @@ -628,6 +645,7 @@ static class ClassLevelExtensionRegistrationWithNullFieldTestCase extends Abstra static class InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension Object extension = "not an extension type"; @@ -635,6 +653,7 @@ static class InstanceLevelExtensionRegistrationWithNonExtensionFieldValueTestCas static class ClassLevelExtensionRegistrationWithNonExtensionFieldValueTestCase extends AbstractTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") @RegisterExtension static Object extension = "not an extension type"; @@ -704,7 +723,7 @@ private static class ExtensionInjector implements TestInstancePostProcessor { @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { // @formatter:off - AnnotationUtils.findAnnotatedFields(testInstance.getClass(), RegisterExtension.class, isCrystalBall).stream() + findAnnotatedFields(testInstance.getClass(), RegisterExtension.class, isCrystalBall).stream() .findFirst() .ifPresent(field -> { try { @@ -727,11 +746,13 @@ static class TwoNestedClassesTestCase { @Nested class A { + @SuppressWarnings("JUnitMalformedDeclaration") @Test void first(Long n) { assertEquals(42L, n); } + @SuppressWarnings("JUnitMalformedDeclaration") @Test void second(Long n) { assertEquals(42L, n); @@ -742,11 +763,13 @@ void second(Long n) { @Nested class B { + @SuppressWarnings("JUnitMalformedDeclaration") @Test void first(Long n) { assertEquals(42L, n); } + @SuppressWarnings("JUnitMalformedDeclaration") @Test void second(Long n) { assertEquals(42L, n); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java index 42037190f24a..567a38431463 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/RepeatedTestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE; import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; @@ -43,7 +44,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.testkit.engine.Events; @@ -120,6 +121,11 @@ static void afterAll() { assertEquals(42, fortyTwo); } + // Can be injected into test class constructors if the test class only has @RepeatedTest methods + public LifecycleMethodTests(RepetitionInfo repetitionInfo) { + assertNotNull(repetitionInfo); + } + @BeforeEach @AfterEach void beforeAndAfterEach(TestInfo testInfo, RepetitionInfo repetitionInfo) { @@ -285,7 +291,7 @@ void failureThreshold3() { void failureThresholdWithConcurrentExecution() { Class testClass = TestCase.class; String methodName = "failureThresholdWithConcurrentExecution"; - Method method = ReflectionUtils.findMethod(testClass, methodName).get(); + Method method = ReflectionSupport.findMethod(testClass, methodName).get(); LauncherDiscoveryRequest request = request()// .selectors(selectMethod(testClass, method))// .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true")// @@ -313,7 +319,7 @@ void failureThresholdWithConcurrentExecution() { private Events executeTest(String methodName) { Class testClass = TestCase.class; - Method method = ReflectionUtils.findMethod(testClass, methodName).get(); + Method method = ReflectionSupport.findMethod(testClass, methodName).get(); return executeTests(selectMethod(testClass, method)).allEvents(); } @@ -331,10 +337,12 @@ void testWithEmptyPattern() { void testWithBlankPattern() { } + @SuppressWarnings("JUnitMalformedDeclaration") @RepeatedTest(-99) void negativeRepeatCount() { } + @SuppressWarnings("JUnitMalformedDeclaration") @RepeatedTest(0) void zeroRepeatCount() { } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java similarity index 92% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java index d7440eb1c985..bf5f4d192f9b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SameThreadTimeoutInvocationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -34,7 +34,8 @@ void resetsInterruptFlag() { var exception = assertThrows(TimeoutException.class, () -> withExecutor(executor -> { var delegate = new EventuallyInterruptibleInvocation(); var duration = new TimeoutDuration(1, NANOSECONDS); - var timeoutInvocation = new SameThreadTimeoutInvocation<>(delegate, duration, executor, () -> "execution"); + var timeoutInvocation = new SameThreadTimeoutInvocation<>(delegate, duration, executor, () -> "execution", + PreInterruptCallbackInvocation.NOOP); timeoutInvocation.proceed(); })); assertFalse(Thread.currentThread().isInterrupted()); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java index 23307e43dd7b..750611a5c67a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/SeparateThreadTimeoutInvocationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -73,7 +73,8 @@ private static SeparateThreadTimeoutInvocation aSeparateThreadInvocation( var namespace = ExtensionContext.Namespace.create(SeparateThreadTimeoutInvocationTests.class); var store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), namespace); var parameters = new TimeoutInvocationParameters<>(invocation, - new TimeoutDuration(PREEMPTIVE_TIMEOUT_MILLIS, MILLISECONDS), () -> "method()"); + new TimeoutDuration(PREEMPTIVE_TIMEOUT_MILLIS, MILLISECONDS), () -> "method()", + PreInterruptCallbackInvocation.NOOP); return (SeparateThreadTimeoutInvocation) new TimeoutInvocationFactory(store) // .create(ThreadMode.SEPARATE_THREAD, parameters); } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java similarity index 92% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java index aab386b973e0..b56f4febb31c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/ServiceLoaderExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java index ff7644a015b2..6a7d687fc1a3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryCleanupTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -192,6 +192,7 @@ static void deleteIfNotNullAndExists(Path dir) throws IOException { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class DefaultFieldCase { @TempDir @@ -203,6 +204,7 @@ void testDefaultField() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class NeverFieldCase { @TempDir(cleanup = NEVER) @@ -214,6 +216,7 @@ void testNeverField() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class AlwaysFieldCase { @TempDir(cleanup = ALWAYS) @@ -225,6 +228,7 @@ void testAlwaysField() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class OnSuccessPassingFieldCase { @TempDir(cleanup = ON_SUCCESS) @@ -236,6 +240,7 @@ void testOnSuccessPassingField() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class OnSuccessFailingFieldCase { @TempDir(cleanup = ON_SUCCESS) @@ -248,6 +253,7 @@ void testOnSuccessFailingField() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) static class OnSuccessFailingStaticFieldCase { @@ -398,6 +404,7 @@ static void afterAll() throws IOException { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class DefaultParameterCase { @Test @@ -406,6 +413,7 @@ void testDefaultParameter(@TempDir Path defaultParameterDir) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class NeverParameterCase { @Test @@ -414,6 +422,7 @@ void testNeverParameter(@TempDir(cleanup = NEVER) Path neverParameterDir) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class AlwaysParameterCase { @Test @@ -422,6 +431,7 @@ void testAlwaysParameter(@TempDir(cleanup = ALWAYS) Path alwaysParameterDir) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class OnSuccessPassingParameterCase { @Test @@ -430,6 +440,7 @@ void testOnSuccessPassingParameter(@TempDir(cleanup = ON_SUCCESS) Path onSuccess } } + @SuppressWarnings("JUnitMalformedDeclaration") static class OnSuccessFailingParameterCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java similarity index 92% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java index 905672bfbec5..338ba7dd286a 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryMetaAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -44,6 +44,7 @@ void annotationOnParameter() { .assertStatistics(stats -> stats.started(1).succeeded(1)); } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnFieldTestCase { @CustomTempDir @@ -56,6 +57,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnParameterTestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java similarity index 87% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java index 49b28d2dcc3a..6200f2e47645 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -54,14 +54,15 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDirFactory; +import org.junit.jupiter.api.io.TempDirFactory.Standard; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; import org.junit.platform.testkit.engine.EngineExecutionResults; -import org.junit.platform.testkit.engine.Events; /** * Integration tests for the legacy behavior of the {@link TempDirectory} - * extension to create a single temp directory per context, i.e. test class or + * extension to create a single temp directory per context, i.e., test class or * method. * * @since 5.4 @@ -74,6 +75,13 @@ protected EngineExecutionResults executeTestsForClass(Class testClass) { return executeTests(requestBuilder(testClass).build()); } + private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, + Class factoryClass) { + return executeTests(requestBuilder(testClass) // + .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // + .build()); + } + @SuppressWarnings("deprecation") private static LauncherDiscoveryRequestBuilder requestBuilder(Class testClass) { return request() // @@ -88,6 +96,7 @@ void resetStaticVariables() { BaseSharedTempDirParameterInjectionTestCase.tempDir = null; BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.clear(); BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.clear(); + BaseConstructorInjectionTestCase.tempDirs.clear(); } @Test @@ -209,6 +218,17 @@ void resolvesSharedTempDirWhenAnnotationIsUsedOnBeforeAllMethodParameterWithTest AnnotationOnBeforeAllMethodParameterWithTestInstancePerClassTestCase.class); } + @Test + @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") + @Order(25) + void resolvesSharedTempDirWhenAnnotationIsUsedOnConstructorWithTestInstancePerClass() { + var results = executeTestsForClass(SharedTempDirsConstructorInjectionPerClassTestCase.class); + + results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2)); + assertThat(BaseConstructorInjectionTestCase.tempDirs.getFirst()).doesNotExist(); + assertThat(BaseConstructorInjectionTestCase.tempDirs.getLast()).doesNotExist(); + } + private void assertSharedTempDirForFieldInjection( Class testClass) { @@ -288,6 +308,17 @@ void resolvesSeparateTempDirWhenAnnotationIsUsedOnAfterAllMethodParameterOnly() assertThat(AnnotationOnAfterAllMethodParameterTestCase.secondTempDir).isNotNull().doesNotExist(); } + @Test + @DisplayName("when @TempDir is used on constructor parameter") + @Order(32) + void resolvesSeparateTempDirsWhenAnnotationIsUsedOnConstructorWithTestInstancePerMethod() { + var results = executeTestsForClass(SeparateTempDirsConstructorInjectionPerMethodTestCase.class); + + results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2)); + assertThat(BaseConstructorInjectionTestCase.tempDirs.getFirst()).doesNotExist(); + assertThat(BaseConstructorInjectionTestCase.tempDirs.getLast()).doesNotExist(); + } + } @Nested @@ -295,25 +326,20 @@ void resolvesSeparateTempDirWhenAnnotationIsUsedOnAfterAllMethodParameterOnly() @TestMethodOrder(OrderAnnotation.class) class DefaultFactory { - private Events executeTestsForClassWithDefaultFactory(Class testClass, - Class factoryClass) { - return TempDirectoryPerContextTests.super.executeTests(requestBuilder(testClass) // - .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // - .build()).testEvents(); - } - @Test @DisplayName("set to Jupiter's default") void supportsStandardDefaultFactory() { - executeTestsForClassWithDefaultFactory(StandardDefaultFactoryTestCase.class, TempDirFactory.Standard.class) // - .assertStatistics(stats -> stats.started(1).succeeded(1)); + var results = executeTestsForClassWithDefaultFactory(StandardDefaultFactoryTestCase.class, Standard.class); + + results.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @DisplayName("set to custom factory") void supportsCustomDefaultFactory() { - executeTestsForClassWithDefaultFactory(NonStandardDefaultFactoryTestCase.class, Factory.class) // - .assertStatistics(stats -> stats.started(1).succeeded(1)); + var results = executeTestsForClassWithDefaultFactory(CustomDefaultFactoryTestCase.class, Factory.class); + + results.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1)); } private static class Factory implements TempDirFactory { @@ -359,8 +385,7 @@ void onlySupportsParametersOfTypePathAndFile() { var results = executeTestsForClass(InvalidTestCase.class); // @formatter:off - TempDirectoryPerContextTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), @@ -369,42 +394,50 @@ void onlySupportsParametersOfTypePathAndFile() { } @Test - @DisplayName("when @TempDir is used on constructor parameter") - @Order(30) - void doesNotSupportTempDirAnnotationOnConstructorParameter() { - var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class); - - assertSingleFailedTest(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); - } - - @Test - @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") - @Order(31) - void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() { - var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class); + @DisplayName("when non-default @TempDir factory is set") + @Order(32) + void doesNotSupportNonDefaultTempDirFactory() { + var results = executeTestsForClass(NonDefaultFactoryTestCase.class); - assertSingleFailedContainer(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Custom @TempDir factory is not supported with junit.jupiter.tempdir.scope=per_context. " + + "Use junit.jupiter.tempdir.factory.default instead."))); + // @formatter:on } @Test - @DisplayName("when @TempDir factory is not Standard") - @Order(32) - void onlySupportsStandardTempDirFactory() { - var results = executeTestsForClass(NonStandardFactoryTestCase.class); + @DisplayName("when default @TempDir factory does not return directory") + @Order(33) + void doesNotSupportCustomDefaultTempDirFactoryNotReturningDirectory() { + var results = executeTestsForClassWithDefaultFactory( + CustomDefaultFactoryNotReturningDirectoryTestCase.class, FactoryNotReturningDirectory.class); // @formatter:off - TempDirectoryPerContextTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), - message("Custom @TempDir factory is not supported with junit.jupiter.tempdir.scope=per_context. " - + "Use junit.jupiter.tempdir.factory.default instead."))); + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory must be a directory") + ) + )); // @formatter:on } + private static class FactoryNotReturningDirectory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return null; + } + } + } @Nested @@ -480,6 +513,7 @@ private void assertResolvesSeparateTempDirs(Class testClass, Deque temp // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class BaseSharedTempDirFieldInjectionTestCase { static Path staticTempDir; @@ -580,6 +614,7 @@ static class AnnotationOnInstanceFieldAndBeforeAllMethodParameterWithTestInstanc } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnPrivateInstanceFieldTestCase { @SuppressWarnings("unused") @@ -593,6 +628,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnPrivateStaticFieldTestCase { @SuppressWarnings("unused") @@ -606,6 +642,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { @SuppressWarnings("unused") @@ -618,6 +655,7 @@ void test1() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { @SuppressWarnings("unused") @@ -630,6 +668,7 @@ void test1() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class BaseSharedTempDirParameterInjectionTestCase { static Path tempDir; @@ -663,29 +702,80 @@ static void check(Path tempDir) { } - static class AnnotationOnConstructorParameterTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") + static class BaseConstructorInjectionTestCase { - AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) { - // never called + static final Deque tempDirs = new LinkedList<>(); + + private final Path tempDir; + + BaseConstructorInjectionTestCase(Path tempDir) { + this.tempDir = tempDir; } @Test - void test() { - // never called + void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @Test + void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception { + check(tempDir); + writeFile(tempDir, testInfo); + } + + @AfterEach + void afterEach(@TempDir Path tempDir) { + check(tempDir); + } + + void check(Path tempDir) { + assertThat(tempDirs.getLast())// + .isNotNull()// + .isSameAs(tempDir)// + .isSameAs(this.tempDir); + assertTrue(Files.exists(tempDir)); } } + static class SeparateTempDirsConstructorInjectionPerMethodTestCase extends BaseConstructorInjectionTestCase { + + SeparateTempDirsConstructorInjectionPerMethodTestCase(@TempDir Path tempDir) { + super(tempDir); + } + + @BeforeEach + void beforeEach(@TempDir Path tempDir) { + for (Path dir : tempDirs) { + assertThat(dir).doesNotExist(); + } + assertThat(tempDirs).doesNotContain(tempDir); + tempDirs.add(tempDir); + check(tempDir); + } + } + @TestInstance(PER_CLASS) - static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase - extends AnnotationOnConstructorParameterTestCase { + static class SharedTempDirsConstructorInjectionPerClassTestCase extends BaseConstructorInjectionTestCase { - AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) { + SharedTempDirsConstructorInjectionPerClassTestCase(@TempDir Path tempDir) { super(tempDir); } + + @BeforeEach + void beforeEach(@TempDir Path tempDir) { + for (Path dir : tempDirs) { + assertThat(dir).isSameAs(tempDir).exists(); + } + tempDirs.add(tempDir); + check(tempDir); + } } - static class NonStandardFactoryTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") + static class NonDefaultFactoryTestCase { @Test void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tempDir) { @@ -703,6 +793,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio } + @SuppressWarnings("JUnitMalformedDeclaration") static class StandardDefaultFactoryTestCase { @Test @@ -712,7 +803,8 @@ void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { } - static class NonStandardDefaultFactoryTestCase { + @SuppressWarnings("JUnitMalformedDeclaration") + static class CustomDefaultFactoryTestCase { @Test void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { @@ -721,6 +813,16 @@ void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { } + @SuppressWarnings("JUnitMalformedDeclaration") + static class CustomDefaultFactoryNotReturningDirectoryTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir Path tempDir) { + // never called + } + + } + static class AnnotationOnBeforeAllMethodParameterTestCase extends BaseSharedTempDirParameterInjectionTestCase { @BeforeAll @@ -743,6 +845,7 @@ void beforeAll(@TempDir Path tempDir) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnAfterAllMethodParameterTestCase { static Path firstTempDir = null; @@ -763,6 +866,7 @@ static void afterAll(@TempDir Path tempDir) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class BaseSeparateTempDirsFieldInjectionTestCase { static final Deque tempDirs = new LinkedList<>(); @@ -816,6 +920,7 @@ static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePe extends SeparateTempDirsWhenUsedOnForEachLifecycleMethodsFieldInjectionTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") static class BaseSeparateTempDirsParameterInjectionTestCase { static final Deque tempDirs = new LinkedList<>(); @@ -862,6 +967,7 @@ static class SeparateTempDirsWhenUsedOnForEachLifecycleMethodsWithTestInstancePe extends SeparateTempDirsWhenUsedOnForEachLifecycleMethodsParameterInjectionTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") static class InvalidTestCase { @Test @@ -870,6 +976,7 @@ void wrongParameterType(@SuppressWarnings("unused") @TempDir String ignored) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FileInjectionTestCase { @TempDir @@ -898,6 +1005,7 @@ private static void writeFile(Path tempDir, TestInfo testInfo) throws IOExceptio } // https://github.com/junit-team/junit5/issues/1748 + @SuppressWarnings("JUnitMalformedDeclaration") static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase(TestInfo testInfo) { @@ -911,6 +1019,7 @@ void test() { } // https://github.com/junit-team/junit5/issues/1801 + @SuppressWarnings("JUnitMalformedDeclaration") static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { @Test @@ -922,6 +1031,7 @@ void deleteTempDir(@TempDir Path tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2046 + @SuppressWarnings("JUnitMalformedDeclaration") static class NonWritableFileDoesNotCauseFailureTestCase { @Test @@ -936,6 +1046,7 @@ void createReadonlyFile(@TempDir Path tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2171 + @SuppressWarnings("JUnitMalformedDeclaration") static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { @Test @@ -949,6 +1060,7 @@ void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2171 + @SuppressWarnings("JUnitMalformedDeclaration") static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { @Test @@ -973,7 +1085,7 @@ private static boolean makeReadOnly(File file) throws IOException { } // https://github.com/junit-team/junit5/issues/2609 - @SuppressWarnings("ResultOfMethodCallIgnored") + @SuppressWarnings({ "ResultOfMethodCallIgnored", "JUnitMalformedDeclaration" }) static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { @Test @@ -1352,6 +1464,7 @@ void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@ } // https://github.com/junit-team/junit5/issues/2079 + @SuppressWarnings("JUnitMalformedDeclaration") static class TempDirUsageInsideNestedClassesTestCase { @TempDir @@ -1384,6 +1497,7 @@ void deeplyNested() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class StaticTempDirUsageInsideNestedClassTestCase { @TempDir diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java similarity index 80% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java index 153146a782af..5eddab66bcad 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,7 @@ import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; @@ -22,8 +23,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; @@ -43,11 +42,9 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.stream.Stream; import com.github.marschall.memoryfilesystem.MemoryFileSystemBuilder; import com.google.common.jimfs.Configuration; @@ -59,12 +56,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestMethodOrder; @@ -83,6 +78,10 @@ import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; import org.junit.jupiter.engine.Constants; import org.junit.jupiter.engine.extension.TempDirectory.FileOperations; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.testkit.engine.EngineExecutionResults; @@ -95,57 +94,80 @@ @DisplayName("TempDirectory extension (per declaration)") class TempDirectoryPerDeclarationTests extends AbstractJupiterTestEngineTests { + private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, + Class factoryClass) { + return executeTests(request() // + .selectors(selectClass(testClass)) // + .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // + .build()); + } + @BeforeEach @AfterEach void resetStaticVariables() { AllPossibleDeclarationLocationsTestCase.tempDirs.clear(); } - @TestFactory + @ParameterizedTest(name = "{0}") + @EnumSource(TestInstance.Lifecycle.class) @DisplayName("resolves separate temp dirs for each annotation declaration") - Stream resolvesSeparateTempDirsForEachAnnotationDeclaration() { - return Arrays.stream(TestInstance.Lifecycle.values()).map( - lifecycle -> dynamicTest("with " + lifecycle + " lifecycle", () -> { - - var results = executeTests(request() // - .selectors(selectClass(AllPossibleDeclarationLocationsTestCase.class)) // - .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, - lifecycle.name()).build()); - - results.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); - - assertThat(AllPossibleDeclarationLocationsTestCase.tempDirs).hasSize(3); - - var classTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("class"); - assertThat(classTempDirs).containsOnlyKeys("staticField1", "staticField2", "beforeAll1", "beforeAll2", - "afterAll1", "afterAll2"); - assertThat(classTempDirs.values()).hasSize(6).doesNotHaveDuplicates(); - - var testATempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testA"); - assertThat(testATempDirs).containsOnlyKeys("staticField1", "staticField2", "instanceField1", - "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2"); - assertThat(testATempDirs.values()).hasSize(10).doesNotHaveDuplicates(); - - var testBTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testB"); - assertThat(testBTempDirs).containsOnlyKeys("staticField1", "staticField2", "instanceField1", - "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", "afterEach2"); - assertThat(testBTempDirs.values()).hasSize(10).doesNotHaveDuplicates(); - - assertThat(testATempDirs).containsEntry("staticField1", classTempDirs.get("staticField1")); - assertThat(testBTempDirs).containsEntry("staticField1", classTempDirs.get("staticField1")); - assertThat(testATempDirs).containsEntry("staticField2", classTempDirs.get("staticField2")); - assertThat(testBTempDirs).containsEntry("staticField2", classTempDirs.get("staticField2")); - - assertThat(testATempDirs).doesNotContainEntry("instanceField1", testBTempDirs.get("instanceField1")); - assertThat(testATempDirs).doesNotContainEntry("instanceField2", testBTempDirs.get("instanceField2")); - assertThat(testATempDirs).doesNotContainEntry("beforeEach1", testBTempDirs.get("beforeEach1")); - assertThat(testATempDirs).doesNotContainEntry("beforeEach2", testBTempDirs.get("beforeEach2")); - assertThat(testATempDirs).doesNotContainEntry("test1", testBTempDirs.get("test1")); - assertThat(testATempDirs).doesNotContainEntry("test2", testBTempDirs.get("test2")); - assertThat(testATempDirs).doesNotContainEntry("afterEach1", testBTempDirs.get("afterEach1")); - assertThat(testATempDirs).doesNotContainEntry("afterEach2", testBTempDirs.get("afterEach2")); - })); + void resolvesSeparateTempDirsForEachAnnotationDeclaration(TestInstance.Lifecycle lifecycle) { + var results = executeTests(request() // + .selectors(selectClass(AllPossibleDeclarationLocationsTestCase.class)) // + .configurationParameter(Constants.DEFAULT_TEST_INSTANCE_LIFECYCLE_PROPERTY_NAME, + lifecycle.name()).build()); + + results.containerEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + + assertThat(AllPossibleDeclarationLocationsTestCase.tempDirs).hasSize(3); + + var classTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("class"); + assertThat(classTempDirs) // + .containsOnlyKeys("staticField1", "staticField2", "beforeAll1", "beforeAll2", "afterAll1", "afterAll2") // + .values().hasSize(6).doesNotHaveDuplicates().allMatch(Files::notExists); + + var testATempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testA"); + assertThat(testATempDirs) // + .containsOnlyKeys("staticField1", "staticField2", "instanceFieldSetViaConstructorInjection", + "instanceField1", "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", + "afterEach2") // + .values().hasSize(11).doesNotHaveDuplicates().allMatch(Files::notExists); + + var testBTempDirs = AllPossibleDeclarationLocationsTestCase.tempDirs.get("testB"); + assertThat(testBTempDirs) // + .containsOnlyKeys("staticField1", "staticField2", "instanceFieldSetViaConstructorInjection", + "instanceField1", "instanceField2", "beforeEach1", "beforeEach2", "test1", "test2", "afterEach1", + "afterEach2") // + .values().hasSize(11).doesNotHaveDuplicates().allMatch(Files::notExists); + + assertThat(testATempDirs).containsEntry("staticField1", classTempDirs.get("staticField1")); + assertThat(testBTempDirs).containsEntry("staticField1", classTempDirs.get("staticField1")); + assertThat(testATempDirs).containsEntry("staticField2", classTempDirs.get("staticField2")); + assertThat(testBTempDirs).containsEntry("staticField2", classTempDirs.get("staticField2")); + + switch (lifecycle) { + case PER_CLASS -> assertThat(testATempDirs) // + .containsEntry("instanceFieldSetViaConstructorInjection", + testBTempDirs.get("instanceFieldSetViaConstructorInjection")); + case PER_METHOD -> assertThat(testATempDirs) // + .doesNotContainEntry("instanceFieldSetViaConstructorInjection", + testBTempDirs.get("instanceFieldSetViaConstructorInjection")); + } + assertThat(testATempDirs).doesNotContainEntry("instanceField1", testBTempDirs.get("instanceField1")); + assertThat(testATempDirs).doesNotContainEntry("instanceField2", testBTempDirs.get("instanceField2")); + assertThat(testATempDirs).doesNotContainEntry("beforeEach1", testBTempDirs.get("beforeEach1")); + assertThat(testATempDirs).doesNotContainEntry("beforeEach2", testBTempDirs.get("beforeEach2")); + assertThat(testATempDirs).doesNotContainEntry("test1", testBTempDirs.get("test1")); + assertThat(testATempDirs).doesNotContainEntry("test2", testBTempDirs.get("test2")); + assertThat(testATempDirs).doesNotContainEntry("afterEach1", testBTempDirs.get("afterEach1")); + assertThat(testATempDirs).doesNotContainEntry("afterEach2", testBTempDirs.get("afterEach2")); + } + + @Test + void supportsConstructorInjectionOnRecords() { + executeTestsForClass(TempDirRecordTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1)); } @Test @@ -213,16 +235,10 @@ void canBeUsedViaStaticFieldInsideNestedTestClasses() { .assertStatistics(stats -> stats.started(2).succeeded(2)); } - @TestFactory + @ParameterizedTest(name = "{0}") + @ValueSource(classes = { UndeletableDirectoryTestCase.class, UndeletableFileTestCase.class }) @DisplayName("only attempts to delete undeletable paths once") - Stream onlyAttemptsToDeleteUndeletablePathsOnce() { - return Stream.of( // - dynamicTest("directory", () -> onlyAttemptsToDeleteUndeletablePathOnce(UndeletableDirectoryTestCase.class)), // - dynamicTest("file", () -> onlyAttemptsToDeleteUndeletablePathOnce(UndeletableFileTestCase.class)) // - ); - } - - private void onlyAttemptsToDeleteUndeletablePathOnce(Class testClass) { + void onlyAttemptsToDeleteUndeletablePathsOnce(Class testClass) { var results = executeTestsForClass(testClass); var tempDir = results.testEvents().reportingEntryPublished().stream().map( @@ -272,8 +288,7 @@ void onlySupportsParametersOfTypePathAndFile() { var results = executeTestsForClass(InvalidTestCase.class); // @formatter:off - TempDirectoryPerDeclarationTests.assertSingleFailedTest(results, - instanceOf(ParameterResolutionException.class), + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), message(m -> m.matches("Failed to resolve parameter \\[java.lang.String .+] in method \\[.+]: .+")), cause( instanceOf(ExtensionConfigurationException.class), @@ -282,23 +297,74 @@ void onlySupportsParametersOfTypePathAndFile() { } @Test - @DisplayName("when @TempDir is used on constructor parameter") - @Order(30) - void doesNotSupportTempDirAnnotationOnConstructorParameter() { - var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class); + @DisplayName("when @TempDir factory does not return directory") + @Order(32) + void doesNotSupportTempDirFactoryNotReturningDirectory() { + var results = executeTestsForClass(FactoryNotReturningDirectoryTestCase.class); + + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory must be a directory") + ) + )); + // @formatter:on + } + + @Test + @DisplayName("when default @TempDir factory does not return directory") + @Order(33) + void doesNotSupportCustomDefaultTempDirFactoryNotReturningDirectory() { + var results = executeTestsForClassWithDefaultFactory( + CustomDefaultFactoryNotReturningDirectoryTestCase.class, FactoryNotReturningDirectory.class); + + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory must be a directory") + ) + )); + // @formatter:on + } + + private static class FactoryNotReturningDirectory implements TempDirFactory { - assertSingleFailedTest(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return null; + } } @Test - @DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)") - @Order(31) - void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() { - var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class); + @DisplayName("when @TempDir factory returns a non-default file system path for a File annotated element") + @Order(34) + void doesNotSupportNonDefaultFileSystemTempDirFactoryOnFileAnnotatedElement() { + var results = executeTestsForClass( + FactoryReturningNonDefaultFileSystemPathForFileAnnotatedElementTestCase.class); - assertSingleFailedContainer(results, ParameterResolutionException.class, - "@TempDir is not supported on constructor parameters. Please use field injection instead."); + // @formatter:off + assertSingleFailedTest(results, instanceOf(ParameterResolutionException.class), + message(m -> m.matches("Failed to resolve parameter \\[.+] in method \\[.+]: .+")), + cause( + instanceOf(ExtensionConfigurationException.class), + message("Failed to create default temp directory"), + cause( + instanceOf(PreconditionViolationException.class), + message("temp directory with non-default file system cannot be injected into " + + File.class.getName() + " target") + ) + )); + // @formatter:on } } @@ -379,14 +445,6 @@ void supportsFactoryWithCustomMetaAnnotation() { @TestMethodOrder(OrderAnnotation.class) class DefaultFactory { - private EngineExecutionResults executeTestsForClassWithDefaultFactory(Class testClass, - Class factoryClass) { - return TempDirectoryPerDeclarationTests.super.executeTests(request() // - .selectors(selectClass(testClass)) // - .configurationParameter(TempDir.DEFAULT_FACTORY_PROPERTY_NAME, factoryClass.getName()) // - .build()); - } - @Test @DisplayName("set to Jupiter's default") void supportsStandardDefaultFactory() { @@ -464,6 +522,7 @@ private static void assertSingleFailedTest(EngineExecutionResults results, Condi // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnPrivateInstanceFieldTestCase { @SuppressWarnings("unused") @@ -477,6 +536,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnPrivateStaticFieldTestCase { @SuppressWarnings("unused") @@ -490,6 +550,7 @@ void test() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnStaticFieldWithUnsupportedTypeTestCase { @SuppressWarnings("unused") @@ -502,6 +563,7 @@ void test1() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class AnnotationOnInstanceFieldWithUnsupportedTypeTestCase { @SuppressWarnings("unused") @@ -514,28 +576,7 @@ void test1() { } - static class AnnotationOnConstructorParameterTestCase { - - AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) { - // never called - } - - @Test - void test() { - // never called - } - - } - - @TestInstance(PER_CLASS) - static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase - extends AnnotationOnConstructorParameterTestCase { - - AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) { - super(tempDir); - } - } - + @SuppressWarnings("JUnitMalformedDeclaration") static class InvalidTestCase { @Test @@ -570,6 +611,7 @@ private static void assertFileAndPathAreNotEqual(File tempDir, Path ref) { } // https://github.com/junit-team/junit5/issues/1748 + @SuppressWarnings("JUnitMalformedDeclaration") static class TempDirectoryDoesNotPreventConstructorParameterResolutionTestCase { @TempDir @@ -587,6 +629,7 @@ void test() { } // https://github.com/junit-team/junit5/issues/1801 + @SuppressWarnings("JUnitMalformedDeclaration") static class UserTempDirectoryDeletionDoesNotCauseFailureTestCase { @Test @@ -598,6 +641,7 @@ void deleteTempDir(@TempDir Path tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2046 + @SuppressWarnings("JUnitMalformedDeclaration") static class NonWritableFileDoesNotCauseFailureTestCase { @Test @@ -612,6 +656,7 @@ void createReadonlyFile(@TempDir Path tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2171 + @SuppressWarnings("JUnitMalformedDeclaration") static class ReadOnlyFileInReadOnlyDirDoesNotCauseFailureTestCase { @Test @@ -625,6 +670,7 @@ void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2171 + @SuppressWarnings("JUnitMalformedDeclaration") static class ReadOnlyFileInDirInReadOnlyDirDoesNotCauseFailureTestCase { @Test @@ -640,7 +686,7 @@ void createReadOnlyFileInReadOnlyDir(@TempDir File tempDir) throws IOException { } // https://github.com/junit-team/junit5/issues/2609 - @SuppressWarnings("ResultOfMethodCallIgnored") + @SuppressWarnings({ "ResultOfMethodCallIgnored", "JUnitMalformedDeclaration" }) static class NonMintPermissionContentInTempDirectoryDoesNotCauseFailureTestCase { @Test @@ -1019,6 +1065,7 @@ void makeTempDirectoryWithNonEmptyFolderContainingNonReadableFileNonExecutable(@ } // https://github.com/junit-team/junit5/issues/2079 + @SuppressWarnings("JUnitMalformedDeclaration") static class TempDirUsageInsideNestedClassesTestCase { @TempDir @@ -1051,6 +1098,7 @@ void deeplyNested() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class StaticTempDirUsageInsideNestedClassTestCase { @TempDir @@ -1077,6 +1125,7 @@ void nested() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("class") static class AllPossibleDeclarationLocationsTestCase { @@ -1088,6 +1137,8 @@ static class AllPossibleDeclarationLocationsTestCase { @TempDir static Path staticField2; + final Path instanceFieldSetViaConstructorInjection; + @TempDir Path instanceField1; @@ -1102,6 +1153,11 @@ static void beforeAll(@TempDir Path param1, @TempDir Path param2, TestInfo testI "beforeAll1", param1, // "beforeAll2", param2 // )); + assertAllTempDirsExist(testInfo); + } + + AllPossibleDeclarationLocationsTestCase(@TempDir Path tempDir) { + this.instanceFieldSetViaConstructorInjection = tempDir; } @BeforeEach @@ -1109,11 +1165,13 @@ void beforeEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { getTempDirs(testInfo).putAll(Map.of( // "staticField1", staticField1, // "staticField2", staticField2, // + "instanceFieldSetViaConstructorInjection", instanceFieldSetViaConstructorInjection, // "instanceField1", instanceField1, // "instanceField2", instanceField2, // "beforeEach1", param1, // "beforeEach2", param2 // )); + assertAllTempDirsExist(testInfo); } @Test @@ -1123,6 +1181,7 @@ void testA(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { "test1", param1, // "test2", param2 // )); + assertAllTempDirsExist(testInfo); } @Test @@ -1132,6 +1191,7 @@ void testB(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { "test1", param1, // "test2", param2 // )); + assertAllTempDirsExist(testInfo); } @AfterEach @@ -1140,6 +1200,7 @@ void afterEach(@TempDir Path param1, @TempDir Path param2, TestInfo testInfo) { "afterEach1", param1, // "afterEach2", param2 // )); + assertAllTempDirsExist(testInfo); } @AfterAll @@ -1148,11 +1209,16 @@ static void afterAll(@TempDir Path param1, @TempDir Path param2, TestInfo testIn "afterAll1", param1, // "afterAll2", param2 // )); + assertAllTempDirsExist(testInfo); } private static Map getTempDirs(TestInfo testInfo) { return tempDirs.computeIfAbsent(testInfo.getDisplayName(), __ -> new LinkedHashMap<>()); } + + private static void assertAllTempDirsExist(TestInfo testInfo) { + assertAll(getTempDirs(testInfo).values().stream().map(tempDir -> () -> assertTrue(Files.exists(tempDir)))); + } } static class UndeletableTestCase { @@ -1181,6 +1247,7 @@ void reportTempDir(TestReporter reporter) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class UndeletableDirectoryTestCase extends UndeletableTestCase { @Test void test() throws Exception { @@ -1188,6 +1255,7 @@ void test() throws Exception { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class UndeletableFileTestCase extends UndeletableTestCase { @Test void test() throws Exception { @@ -1195,6 +1263,7 @@ void test() throws Exception { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryWithTestMethodNameAsPrefixTestCase { @Test @@ -1215,6 +1284,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio } // https://github.com/junit-team/junit5/issues/2088 + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryWithCustomParentDirectoryTestCase { @Test @@ -1240,6 +1310,7 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio } + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryWithMemoryFileSystemTestCase { @Test @@ -1268,6 +1339,7 @@ public void close() throws IOException { } + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryWithJimfsTestCase { @Test @@ -1296,6 +1368,7 @@ public void close() throws IOException { } + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryWithAnnotatedElementNameAsPrefixTestCase { @TempDir(factory = Factory.class) @@ -1323,6 +1396,7 @@ private static String getName(AnnotatedElement element) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryWithCustomMetaAnnotationTestCase { @TempDirForField @@ -1369,6 +1443,51 @@ public Path createTempDirectory(AnnotatedElementContext elementContext, Extensio } + @SuppressWarnings("JUnitMalformedDeclaration") + static class FactoryNotReturningDirectoryTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) Path tempDir) { + // never called + } + + private static class Factory implements TempDirFactory { + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) { + return null; + } + } + + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class FactoryReturningNonDefaultFileSystemPathForFileAnnotatedElementTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir(factory = Factory.class) File tempDir) { + // never called + } + + private static class Factory implements TempDirFactory { + + private final FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix()); + + @Override + public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext) + throws Exception { + return Files.createTempDirectory(fileSystem.getPath("/"), "prefix"); + } + + @Override + public void close() throws IOException { + fileSystem.close(); + } + } + + } + + @SuppressWarnings("JUnitMalformedDeclaration") static class StandardDefaultFactoryTestCase { @Test @@ -1380,6 +1499,7 @@ void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class CustomDefaultFactoryTestCase { @Test @@ -1391,6 +1511,7 @@ void test(@TempDir Path tempDir1, @TempDir Path tempDir2) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class CustomDefaultFactoryWithStandardDeclarationTestCase { @Test @@ -1402,4 +1523,22 @@ void test(@TempDir Path tempDir1, @TempDir(factory = Standard.class) Path tempDi } + @SuppressWarnings("JUnitMalformedDeclaration") + static class CustomDefaultFactoryNotReturningDirectoryTestCase { + + @Test + void test(@SuppressWarnings("unused") @TempDir Path tempDir) { + // never called + } + + } + + @SuppressWarnings("JUnitMalformedDeclaration") + record TempDirRecordTestCase(@TempDir Path tempDir) { + @Test + void shouldExists() { + assertTrue(Files.exists(tempDir)); + } + } + } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java index 659dfca5771a..fc3f84f3a146 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPreconditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -87,6 +87,7 @@ void finalInstanceFieldIsNotSupported() { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class ParameterTypeTestCase { @Test @@ -98,6 +99,7 @@ void invalidTempDirType(@TempDir String text) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FinalStaticFieldTestCase { static final @TempDir Path path = Paths.get("."); @@ -107,6 +109,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FinalInstanceFieldTestCase { final @TempDir Path path = Paths.get("."); diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java similarity index 98% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java index 71a5b9e65934..4fbebbcc1102 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestExecutionExceptionHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -133,6 +133,7 @@ void severalHandlersAreCalledInOrder() { // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class ATestCase { @Test diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java similarity index 83% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java index 2a66c6f4ae2a..835b5e518ddd 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInfoParameterResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -35,8 +35,13 @@ @Tag("class-tag") class TestInfoParameterResolverTests { - private static List allDisplayNames = Arrays.asList("defaultDisplayName(TestInfo)", "custom display name", - "getTags(TestInfo)", "customDisplayNameThatIsEmpty(TestInfo)"); + private static final List allDisplayNames = Arrays.asList("defaultDisplayName(TestInfo)", + "custom display name", "getTags(TestInfo)", "customDisplayNameThatIsEmpty(TestInfo)"); + + public TestInfoParameterResolverTests(TestInfo testInfo) { + assertThat(testInfo.getTestClass()).contains(TestInfoParameterResolverTests.class); + assertThat(testInfo.getTestMethod()).isPresent(); + } @Test void defaultDisplayName(TestInfo testInfo) { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java similarity index 82% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java index b338cb0567a8..8e670d0a3601 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstanceFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import static org.junit.platform.commons.util.ClassUtils.nullSafeToString; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; @@ -43,8 +46,9 @@ import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstantiationException; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.engine.Constants; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; -import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.testkit.engine.EngineExecutionResults; /** @@ -276,7 +280,8 @@ void instanceFactoryOnTopLevelTestClass() { // @formatter:off assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: ParentTestCase", - "parentTest" + "parentTest", + "close ParentTestCase" ); // @formatter:on } @@ -305,8 +310,10 @@ void inheritedFactoryInTestClassHierarchy() { assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: InheritedFactoryTestCase", "parentTest", + "close InheritedFactoryTestCase", "FooInstanceFactory instantiated: InheritedFactoryTestCase", - "childTest" + "childTest", + "close InheritedFactoryTestCase" ); // @formatter:on } @@ -324,17 +331,23 @@ void instanceFactoriesInNestedClassStructureAreInherited() { // OuterTestCase "FooInstanceFactory instantiated: OuterTestCase", "outerTest", + "close OuterTestCase", // InnerTestCase "FooInstanceFactory instantiated: OuterTestCase", "FooInstanceFactory instantiated: InnerTestCase", "innerTest1", + "close InnerTestCase", + "close OuterTestCase", // InnerInnerTestCase "FooInstanceFactory instantiated: OuterTestCase", "FooInstanceFactory instantiated: InnerTestCase", "FooInstanceFactory instantiated: InnerInnerTestCase", - "innerTest2" + "innerTest2", + "close InnerInnerTestCase", + "close InnerTestCase", + "close OuterTestCase" ); // @formatter:on } @@ -349,7 +362,8 @@ void instanceFactoryRegisteredViaTestInterface() { // @formatter:off assertThat(callSequence).containsExactly( "FooInstanceFactory instantiated: FactoryFromInterfaceTestCase", - "test" + "test", + "close FactoryFromInterfaceTestCase" ); // @formatter:on } @@ -386,13 +400,71 @@ void instanceFactoryWithPerClassLifecycle() { "test1", "@BeforeEach", "test2", - "@AfterAll" + "@AfterAll", + "close PerClassLifecycleTestCase" + ); + // @formatter:on + } + + @Test + void instanceFactoryWithLegacyContext() { + EngineExecutionResults executionResults = executeTestsForClass(LegacyContextTestCase.class); + + assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "outerTest", + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "LegacyInstanceFactory instantiated: InnerTestCase", + "innerTest1", + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "LegacyInstanceFactory instantiated: InnerTestCase", + "innerTest2", + "close InnerTestCase", + "close InnerTestCase", + "close LegacyContextTestCase", + "close LegacyContextTestCase", + "close LegacyContextTestCase" + ); + // @formatter:on + } + + @Test + void instanceFactoryWithLegacyContextAndChangedDefaultScope() { + var executionResults = executeTests(request() // + .selectors(selectClass(LegacyContextTestCase.class)) // + .configurationParameter( + Constants.DEFAULT_TEST_CLASS_INSTANCE_CONSTRUCTION_EXTENSION_CONTEXT_SCOPE_PROPERTY_NAME, + TEST_METHOD.name())); + + assertEquals(3, executionResults.testEvents().started().count(), "# tests started"); + assertEquals(3, executionResults.testEvents().succeeded().count(), "# tests succeeded"); + + // @formatter:off + assertThat(callSequence).containsExactly( + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "outerTest", + "close LegacyContextTestCase", + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "LegacyInstanceFactory instantiated: InnerTestCase", + "innerTest1", + "close InnerTestCase", + "close LegacyContextTestCase", + "LegacyInstanceFactory instantiated: LegacyContextTestCase", + "LegacyInstanceFactory instantiated: InnerTestCase", + "innerTest2", + "close InnerTestCase", + "close LegacyContextTestCase" ); // @formatter:on } // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooInstanceFactory.class, BarInstanceFactory.class }) static class MultipleFactoriesRegisteredOnSingleTestCase { @@ -402,6 +474,7 @@ void testShouldNotBeCalled() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(NullTestInstanceFactory.class) static class NullTestInstanceFactoryTestCase { @@ -415,6 +488,7 @@ void testShouldNotBeCalled() { static class PerClassLifecycleNullTestInstanceFactoryTestCase extends NullTestInstanceFactoryTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BogusTestInstanceFactory.class) static class BogusTestInstanceFactoryTestCase { @@ -428,6 +502,7 @@ void testShouldNotBeCalled() { static class PerClassLifecycleBogusTestInstanceFactoryTestCase extends BogusTestInstanceFactoryTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExplosiveTestInstanceFactory.class) static class ExplosiveTestInstanceFactoryTestCase { @@ -450,6 +525,7 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(MultipleConstructorsTestInstanceFactory.class) static class MultipleConstructorsTestCase { @@ -469,6 +545,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooInstanceFactory.class) static class ParentTestCase { @@ -478,6 +555,7 @@ void parentTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class InheritedFactoryTestCase extends ParentTestCase { @Test @@ -486,6 +564,7 @@ void childTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BarInstanceFactory.class) static class MultipleFactoriesRegisteredWithinClassHierarchyTestCase extends ParentTestCase { @@ -495,6 +574,7 @@ void childTest() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooInstanceFactory.class) static class OuterTestCase { @@ -522,6 +602,7 @@ void innerTest2() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooInstanceFactory.class) static class MultipleFactoriesRegisteredWithinNestedClassStructureTestCase { @@ -543,6 +624,7 @@ void innerTest() { interface TestInterface { } + @SuppressWarnings("JUnitMalformedDeclaration") static class FactoryFromInterfaceTestCase implements TestInterface { @Test @@ -551,6 +633,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class LambdaFactoryTestCase { private final String text; @@ -573,6 +656,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooInstanceFactory.class) @TestInstance(PER_CLASS) static class PerClassLifecycleTestCase { @@ -609,6 +693,31 @@ void afterAll() { } } + @SuppressWarnings("JUnitMalformedDeclaration") + @ExtendWith(LegacyInstanceFactory.class) + static class LegacyContextTestCase { + + @Test + void outerTest() { + callSequence.add("outerTest"); + } + + @Nested + class InnerTestCase { + + @Test + void innerTest1() { + callSequence.add("innerTest1"); + } + + @Test + void innerTest2() { + callSequence.add("innerTest2"); + } + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ProxyTestInstanceFactory.class) @TestInstance(PER_CLASS) static class ProxiedTestCase { @@ -633,18 +742,33 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte Class testClass = factoryContext.getTestClass(); instantiated(getClass(), testClass); + extensionContext.getStore(ExtensionContext.Namespace.create(this)).put(new Object(), + (ExtensionContext.Store.CloseableResource) () -> callSequence.add( + "close " + testClass.getSimpleName())); + if (factoryContext.getOuterInstance().isPresent()) { - return ReflectionUtils.newInstance(testClass, factoryContext.getOuterInstance().get()); + return ReflectionSupport.newInstance(testClass, factoryContext.getOuterInstance().get()); } // else - return ReflectionUtils.newInstance(testClass); + return ReflectionSupport.newInstance(testClass); } } private static class FooInstanceFactory extends AbstractTestInstanceFactory { + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } } private static class BarInstanceFactory extends AbstractTestInstanceFactory { + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } + } + + private static class LegacyInstanceFactory extends AbstractTestInstanceFactory { } /** @@ -698,7 +822,7 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte try (var testClassLoader = TestClassLoader.forClasses(testClass)) { // Load test class from different class loader Class clazz = testClassLoader.loadClass(className); - return ReflectionUtils.newInstance(clazz); + return ReflectionSupport.newInstance(clazz); } catch (Exception ex) { throw new RuntimeException("Failed to load class [" + className + "]", ex); @@ -706,8 +830,8 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte } } - private static boolean instantiated(Class factoryClass, Class testClass) { - return callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); + private static void instantiated(Class factoryClass, Class testClass) { + callSequence.add(factoryClass.getSimpleName() + " instantiated: " + testClass.getSimpleName()); } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java index fec5ad33bb04..974d82af7679 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorAndPreDestroyCallbackTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -120,6 +120,7 @@ private void assertPostProcessorAndPreDestroyCallbacks(Class testClass, int t // ------------------------------------------------------------------------- // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith({ FooTestInstanceCallbacks.class, BarTestInstanceCallbacks.class }) static class TopLevelTestCase { @@ -130,6 +131,7 @@ void test() { } // Must NOT be private; otherwise, the @Test method gets discovered but never executed. + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(BazTestInstanceCallbacks.class) static class SecondLevelTestCase extends TopLevelTestCase { @@ -140,6 +142,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(QuuxTestInstanceCallbacks.class) static class ThirdLevelTestCase extends SecondLevelTestCase { @@ -150,6 +153,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExceptionThrowingTestInstancePreDestroyCallback.class) static class ExceptionInTestInstancePreDestroyCallbackTestCase { @@ -159,6 +163,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExceptionThrowingTestInstancePostProcessor.class) static class ExceptionInTestInstancePostProcessorTestCase { @@ -168,6 +173,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooTestInstanceCallbacks.class) static class ExceptionInTestClassConstructorTestCase { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java new file mode 100644 index 000000000000..8299c1cbff1e --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePostProcessorTests.java @@ -0,0 +1,236 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.jupiter.engine.extension; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; + +/** + * Integration tests that verify support for {@link TestInstancePostProcessor}. + * + * @since 5.0 + */ +class TestInstancePostProcessorTests extends AbstractJupiterTestEngineTests { + + private static final List callSequence = new ArrayList<>(); + + @BeforeEach + void resetCallSequence() { + callSequence.clear(); + } + + @Test + void instancePostProcessorsInNestedClasses() { + executeTestsForClass(OuterTestCase.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + + // OuterTestCase + "foo:OuterTestCase", + "legacy:OuterTestCase", + "beforeOuterMethod", + "testOuter", + "close:foo:OuterTestCase", + + // InnerTestCase + + "foo:OuterTestCase", + "legacy:OuterTestCase", + "foo:InnerTestCase", + "legacy:InnerTestCase", + "bar:InnerTestCase", + "beforeOuterMethod", + "beforeInnerMethod", + "testInner", + "close:bar:InnerTestCase", + "close:foo:InnerTestCase", + "close:foo:OuterTestCase", + "close:legacy:InnerTestCase", + "close:legacy:OuterTestCase", + "close:legacy:OuterTestCase" + ); + // @formatter:on + } + + @Test + void testSpecificTestInstancePostProcessorIsCalled() { + executeTestsForClass(TestCaseWithTestSpecificTestInstancePostProcessor.class).testEvents()// + .assertStatistics(stats -> stats.started(2).succeeded(2)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "foo:TestCaseWithTestSpecificTestInstancePostProcessor", + "legacy:TestCaseWithTestSpecificTestInstancePostProcessor", + "beforeEachMethod", + "test1", + "close:foo:TestCaseWithTestSpecificTestInstancePostProcessor", + "beforeEachMethod", + "test2", + "close:legacy:TestCaseWithTestSpecificTestInstancePostProcessor" + ); + // @formatter:on + } + + // ------------------------------------------------------------------- + + @SuppressWarnings("JUnitMalformedDeclaration") + @ExtendWith(FooInstancePostProcessor.class) + @ExtendWith(LegacyInstancePostProcessor.class) + static class OuterTestCase implements Named { + + private final Map outerNames = new HashMap<>(); + + @Override + public void setName(String source, String name) { + outerNames.put(source, name); + } + + @BeforeEach + void beforeOuterMethod() { + callSequence.add("beforeOuterMethod"); + } + + @Test + void testOuter() { + assertEquals( + Map.of("foo", OuterTestCase.class.getSimpleName(), "legacy", OuterTestCase.class.getSimpleName()), + outerNames); + callSequence.add("testOuter"); + } + + @Nested + @ExtendWith(BarInstancePostProcessor.class) + class InnerTestCase implements Named { + + private final Map innerNames = new HashMap<>(); + + @Override + public void setName(String source, String name) { + innerNames.put(source, name); + } + + @BeforeEach + void beforeInnerMethod() { + callSequence.add("beforeInnerMethod"); + } + + @Test + void testInner() { + assertEquals( + Map.of("foo", InnerTestCase.class.getSimpleName(), "legacy", OuterTestCase.class.getSimpleName()), + outerNames); + assertEquals(Map.of("foo", InnerTestCase.class.getSimpleName(), "bar", + InnerTestCase.class.getSimpleName(), "legacy", InnerTestCase.class.getSimpleName()), innerNames); + callSequence.add("testInner"); + } + } + + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class TestCaseWithTestSpecificTestInstancePostProcessor implements Named { + + private final Map names = new HashMap<>(); + + @Override + public void setName(String source, String name) { + names.put(source, name); + } + + @BeforeEach + void beforeEachMethod() { + callSequence.add("beforeEachMethod"); + } + + @ExtendWith(FooInstancePostProcessor.class) + @ExtendWith(LegacyInstancePostProcessor.class) + @Test + void test1() { + callSequence.add("test1"); + assertEquals(Map.of("foo", getClass().getSimpleName(), "legacy", getClass().getSimpleName()), names); + } + + @Test + void test2() { + callSequence.add("test2"); + assertEquals(Map.of(), names); + } + } + + static abstract class AbstractInstancePostProcessor implements TestInstancePostProcessor { + private final String name; + + AbstractInstancePostProcessor(String name) { + this.name = name; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + if (testInstance instanceof Named) { + ((Named) testInstance).setName(name, context.getRequiredTestClass().getSimpleName()); + } + String instanceType = testInstance.getClass().getSimpleName(); + callSequence.add(name + ":" + instanceType); + context.getStore(ExtensionContext.Namespace.create(this)).put(new Object(), + (ExtensionContext.Store.CloseableResource) () -> callSequence.add( + "close:" + name + ":" + instanceType)); + } + } + + static class FooInstancePostProcessor extends AbstractInstancePostProcessor { + FooInstancePostProcessor() { + super("foo"); + } + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } + } + + static class BarInstancePostProcessor extends AbstractInstancePostProcessor { + BarInstancePostProcessor() { + super("bar"); + } + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } + } + + static class LegacyInstancePostProcessor extends AbstractInstancePostProcessor { + LegacyInstancePostProcessor() { + super("legacy"); + } + } + + private interface Named { + + void setName(String source, String name); + } + +} diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java similarity index 63% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java index fdf323d21291..a73e0c494505 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreConstructCallbackTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,7 @@ package org.junit.jupiter.engine.extension; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.extension.TestInstantiationAwareExtension.ExtensionContextScope.TEST_METHOD; import java.util.ArrayList; import java.util.List; @@ -59,12 +60,14 @@ void instancePreConstruct() { "beforeEach", "test1", "afterEach", + "close: name=foo, testClass=InstancePreConstructTestCase", "PreConstructCallback: name=foo, testClass=InstancePreConstructTestCase, outerInstance: null", "constructor", "beforeEach", "test2", "afterEach", + "close: name=foo, testClass=InstancePreConstructTestCase", "afterAll" ); @@ -86,6 +89,7 @@ void factoryPreConstruct() { "beforeEach", "test1", "afterEach", + "close: name=foo, testClass=FactoryPreConstructTestCase", "PreConstructCallback: name=foo, testClass=FactoryPreConstructTestCase, outerInstance: null", "testInstanceFactory", @@ -93,6 +97,7 @@ void factoryPreConstruct() { "beforeEach", "test2", "afterEach", + "close: name=foo, testClass=FactoryPreConstructTestCase", "afterAll" ); @@ -113,12 +118,14 @@ void preConstructInNested() { "beforeEach", "outerTest1", "afterEach", + "close: name=foo, testClass=PreConstructInNestedTestCase", "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", "constructor", "beforeEach", "outerTest2", "afterEach", + "close: name=foo, testClass=PreConstructInNestedTestCase", "PreConstructCallback: name=foo, testClass=PreConstructInNestedTestCase, outerInstance: null", "constructor", @@ -133,6 +140,11 @@ void preConstructInNested() { "afterEachInner", "afterEach", + "close: name=baz, testClass=InnerTestCase", + "close: name=bar, testClass=InnerTestCase", + "close: name=foo, testClass=InnerTestCase", + "close: name=foo, testClass=PreConstructInNestedTestCase", + "afterAll" ); // @formatter:on @@ -150,6 +162,7 @@ void preConstructOnMethod() { "beforeEach", "test1", "afterEach", + "close: name=foo, testClass=PreConstructOnMethod", "constructor", "beforeEach", @@ -172,7 +185,57 @@ void preConstructWithClassLifecycle() { "beforeEach", "test1", "beforeEach", - "test2" + "test2", + "close: name=bar, testClass=PreConstructWithClassLifecycle", + "close: name=foo, testClass=PreConstructWithClassLifecycle" + ); + // @formatter:on + } + + @Test + void legacyPreConstruct() { + executeTestsForClass(LegacyPreConstructTestCase.class).testEvents()// + .assertStatistics(stats -> stats.started(3).succeeded(3)); + + // @formatter:off + assertThat(callSequence).containsExactly( + "beforeAll", + + "PreConstructCallback: name=foo, testClass=LegacyPreConstructTestCase, outerInstance: null", + "PreConstructCallback: name=legacy, testClass=LegacyPreConstructTestCase, outerInstance: null", + "constructor", + "beforeEach", + "outerTest1", + "afterEach", + "close: name=foo, testClass=LegacyPreConstructTestCase", + + "PreConstructCallback: name=foo, testClass=LegacyPreConstructTestCase, outerInstance: null", + "PreConstructCallback: name=legacy, testClass=LegacyPreConstructTestCase, outerInstance: null", + "constructor", + "beforeEach", + "outerTest2", + "afterEach", + "close: name=foo, testClass=LegacyPreConstructTestCase", + + "PreConstructCallback: name=foo, testClass=LegacyPreConstructTestCase, outerInstance: null", + "PreConstructCallback: name=legacy, testClass=LegacyPreConstructTestCase, outerInstance: null", + "constructor", + "PreConstructCallback: name=foo, testClass=InnerTestCase, outerInstance: LegacyPreConstructTestCase", + "PreConstructCallback: name=legacy, testClass=InnerTestCase, outerInstance: LegacyPreConstructTestCase", + "constructorInner", + "beforeEach", + "beforeEachInner", + "innerTest1", + "afterEachInner", + "afterEach", + "close: name=foo, testClass=InnerTestCase", + "close: name=foo, testClass=LegacyPreConstructTestCase", + + "close: name=legacy, testClass=InnerTestCase", + "afterAll", + "close: name=legacy, testClass=LegacyPreConstructTestCase", + "close: name=legacy, testClass=LegacyPreConstructTestCase", + "close: name=legacy, testClass=LegacyPreConstructTestCase" ); // @formatter:on } @@ -183,6 +246,7 @@ protected static void record(String event) { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) static class InstancePreConstructTestCase extends CallSequenceRecordingTestCase { @@ -221,6 +285,7 @@ static void afterAll() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) static class FactoryPreConstructTestCase extends CallSequenceRecordingTestCase { @@ -265,6 +330,7 @@ static void afterAll() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) static class PreConstructInNestedTestCase extends CallSequenceRecordingTestCase { @@ -342,6 +408,7 @@ void afterEachInner() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class PreConstructOnMethod extends CallSequenceRecordingTestCase { PreConstructOnMethod() { record("constructor"); @@ -369,6 +436,7 @@ void afterEach() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) @ExtendWith(InstancePreConstructCallbackRecordingBar.class) @@ -393,6 +461,74 @@ void test2() { } } + @SuppressWarnings("JUnitMalformedDeclaration") + @ExtendWith(InstancePreConstructCallbackRecordingFoo.class) + @ExtendWith(InstancePreConstructCallbackRecordingLegacy.class) + static class LegacyPreConstructTestCase extends CallSequenceRecordingTestCase { + + LegacyPreConstructTestCase() { + record("constructor"); + } + + @BeforeAll + static void beforeAll() { + record("beforeAll"); + } + + @BeforeEach + void beforeEach() { + record("beforeEach"); + } + + @Test + void outerTest1() { + record("outerTest1"); + } + + @Test + void outerTest2() { + record("outerTest2"); + } + + @AfterEach + void afterEach() { + record("afterEach"); + } + + @AfterAll + static void afterAll() { + record("afterAll"); + } + + @Override + public String toString() { + return "LegacyPreConstructTestCase"; + } + + @Nested + class InnerTestCase extends CallSequenceRecordingTestCase { + + InnerTestCase() { + record("constructorInner"); + } + + @BeforeEach + void beforeEachInner() { + record("beforeEachInner"); + } + + @Test + void innerTest1() { + record("innerTest1"); + } + + @AfterEach + void afterEachInner() { + record("afterEachInner"); + } + } + } + static abstract class AbstractTestInstancePreConstructCallback implements TestInstancePreConstructCallback { private final String name; @@ -404,10 +540,21 @@ static abstract class AbstractTestInstancePreConstructCallback implements TestIn public void preConstructTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext context) { assertThat(context.getTestInstance()).isNotPresent(); assertThat(context.getTestClass()).isPresent(); - assertThat(factoryContext.getTestClass()).isSameAs(context.getTestClass().get()); - callSequence.add( - "PreConstructCallback: name=" + name + ", testClass=" + factoryContext.getTestClass().getSimpleName() - + ", outerInstance: " + factoryContext.getOuterInstance().orElse(null)); + if (name.equals("legacy")) { + assertThat(factoryContext.getTestClass()).isSameAs(context.getTestClass().get()); + } + else if (context.getTestInstanceLifecycle().orElse(null) != TestInstance.Lifecycle.PER_CLASS) { + assertThat(context.getTestMethod()).isPresent(); + } + else { + assertThat(context.getTestMethod()).isEmpty(); + } + String testClass = factoryContext.getTestClass().getSimpleName(); + callSequence.add("PreConstructCallback: name=" + name + ", testClass=" + testClass + ", outerInstance: " + + factoryContext.getOuterInstance().orElse(null)); + context.getStore(ExtensionContext.Namespace.create(this)).put(new Object(), + (ExtensionContext.Store.CloseableResource) () -> callSequence.add( + "close: name=" + name + ", testClass=" + testClass)); } } @@ -415,18 +562,39 @@ static class InstancePreConstructCallbackRecordingFoo extends AbstractTestInstan InstancePreConstructCallbackRecordingFoo() { super("foo"); } + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } } static class InstancePreConstructCallbackRecordingBar extends AbstractTestInstancePreConstructCallback { InstancePreConstructCallbackRecordingBar() { super("bar"); } + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } } static class InstancePreConstructCallbackRecordingBaz extends AbstractTestInstancePreConstructCallback { InstancePreConstructCallbackRecordingBaz() { super("baz"); } + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return TEST_METHOD; + } + } + + static class InstancePreConstructCallbackRecordingLegacy extends AbstractTestInstancePreConstructCallback { + InstancePreConstructCallbackRecordingLegacy() { + super("legacy"); + } } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java similarity index 96% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java index a417a00e8db7..f2d05a8dc002 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -104,6 +104,7 @@ void setDestroyed() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(FooInstancePreDestroyCallback.class) static class OuterTestCase extends Destroyable { @@ -136,6 +137,7 @@ void testInner() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithTestSpecificTestInstancePreDestroyCallback extends Destroyable { @BeforeEach @@ -151,6 +153,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestInstance(PER_CLASS) @ExtendWith(FooInstancePreDestroyCallback.class) @ExtendWith(BarInstancePreDestroyCallback.class) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java similarity index 72% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java index 906dfb2d5443..1a96f9639b98 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestInstancePreDestroyCallbackUtilityMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.jupiter.engine.extension; -import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.reportEntry; @@ -18,36 +17,33 @@ import static org.junit.platform.testkit.engine.EventConditions.test; import java.util.Map; -import java.util.stream.Stream; -import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.jupiter.api.extension.TestInstancePreDestroyCallback; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; public class TestInstancePreDestroyCallbackUtilityMethodTests extends AbstractJupiterTestEngineTests { - @TestFactory - Stream destroysWhatWasPostProcessed() { - var testClasses = Stream.of(PerMethodLifecycleOnAllLevels.class, PerMethodWithinPerClassLifecycle.class, - PerClassWithinPerMethodLifecycle.class, PerClassLifecycleOnAllLevels.class); - return testClasses.map(testClass -> dynamicTest( // - testClass.getSimpleName(), // - () -> executeTestsForClass(testClass).allEvents().debug() // - .assertStatistics(stats -> stats.reportingEntryPublished(4)) // - .assertEventsMatchLooselyInOrder( // - reportEntry(Map.of("post-process", testClass.getSimpleName())), - reportEntry(Map.of("post-process", "Inner")), // - event(test(), started()), // - reportEntry(Map.of("pre-destroy", "Inner")), // - reportEntry(Map.of("pre-destroy", testClass.getSimpleName())) // - ))); + @ParameterizedTest(name = "{0}") + @ValueSource(classes = { PerMethodLifecycleOnAllLevels.class, PerMethodWithinPerClassLifecycle.class, + PerClassWithinPerMethodLifecycle.class, PerClassLifecycleOnAllLevels.class }) + void destroysWhatWasPostProcessed(Class testClass) { + executeTestsForClass(testClass).allEvents().debug() // + .assertStatistics(stats -> stats.reportingEntryPublished(4)) // + .assertEventsMatchLooselyInOrder( // + reportEntry(Map.of("post-process", testClass.getSimpleName())), + reportEntry(Map.of("post-process", "Inner")), // + event(test(), started()), // + reportEntry(Map.of("pre-destroy", "Inner")), // + reportEntry(Map.of("pre-destroy", testClass.getSimpleName())) // + ); } @ExtendWith(TestInstanceLifecycleExtension.class) diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java similarity index 90% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java index 913e310c4e93..f785fb791744 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestReporterParameterResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.ParameterContext; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.0 @@ -49,7 +49,7 @@ void resolve() { } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionUtils.findMethod(Sample.class, methodName, parameterTypes).get(); + Method method = ReflectionSupport.findMethod(Sample.class, methodName, parameterTypes).get(); return method.getParameters()[0]; } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java similarity index 86% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java index b9763b382f71..05d318c12418 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TestWatcherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,8 +11,8 @@ package org.junit.jupiter.engine.extension; import static java.util.function.Predicate.not; -import static java.util.stream.Collectors.toUnmodifiableList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -42,8 +42,11 @@ import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.TestWatcher; import org.junit.jupiter.api.fixtures.TrackLogRecords; @@ -60,14 +63,15 @@ */ class TestWatcherTests extends AbstractJupiterTestEngineTests { - private static final List testWatcherMethodNames = Arrays.stream(TestWatcher.class.getDeclaredMethods())// - .filter(not(Method::isSynthetic))// - .map(Method::getName)// - .collect(toUnmodifiableList()); + private static final List testWatcherMethodNames = Arrays.stream(TestWatcher.class.getDeclaredMethods()) // + .filter(not(Method::isSynthetic)) // + .map(Method::getName) // + .toList(); @BeforeEach void clearResults() { TrackingTestWatcher.results.clear(); + DataRetrievingTestWatcher.results.clear(); } @Test @@ -168,6 +172,16 @@ void testWatcherSemanticsWhenRegisteredAtMethodLevel() { assertThat(TrackingTestWatcher.results.get("testDisabled")).containsExactly("test", "repeatedTest"); } + @Test + void testWatcherCanRetrieveDataFromTheExtensionContextStore() { + Class testClass = DataRetrievingTestWatcherTestCase.class; + EngineExecutionResults results = executeTestsForClass(testClass); + + results.testEvents().assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + + assertThat(DataRetrievingTestWatcher.results).containsExactly(entry("key", "enigma")); + } + private void assertCommonStatistics(EngineExecutionResults results) { results.containerEvents().assertStatistics(stats -> stats.started(3).succeeded(3).failed(0)); results.testEvents().assertStatistics(stats -> stats.skipped(2).started(6).succeeded(2).aborted(2).failed(2)); @@ -341,6 +355,7 @@ static class TestInstancePerMethodInstanceLevelTestWatcherTestCase extends Abstr TestWatcher watcher = new TrackingTestWatcher(); } + @SuppressWarnings("JUnitMalformedDeclaration") static class MethodLevelTestWatcherTestCase extends AbstractDisabledMethodsTestCase { @Override @@ -415,4 +430,41 @@ public void testFailed(ExtensionContext context, Throwable cause) { } + /** + * {@link TestWatcher} that retrieves data from the {@link ExtensionContext.Store}. + * @see #3944 + */ + static class DataRetrievingTestWatcher implements BeforeTestExecutionCallback, TestWatcher { + + private static final Namespace NAMESPACE = Namespace.create(DataRetrievingTestWatcher.class); + + private static final String KEY = "key"; + + private static final Map results = new HashMap<>(); + + @Override + public void beforeTestExecution(ExtensionContext context) throws Exception { + getStore(context).put(KEY, "enigma"); + } + + @Override + public void testSuccessful(ExtensionContext context) { + results.put(KEY, getStore(context).get(KEY, String.class)); + } + + private static Store getStore(ExtensionContext context) { + return context.getStore(NAMESPACE); + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ExtendWith(DataRetrievingTestWatcher.class) + static class DataRetrievingTestWatcherTestCase { + + @Test + public void test() { + //no-op + } + } + } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java similarity index 99% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java index 52dd96bb08f3..ffda80ba26a3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java index 14fe2ce0f5e0..2c727766eee8 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java index cf20a202ad4a..a950ca372255 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutDurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java similarity index 97% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java index 34a621391141..3c5c5a29602e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExceptionFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java similarity index 87% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java index a51b102839d0..8cfd1f1c24d3 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.Timeout.ThreadMode.SAME_THREAD; @@ -32,7 +33,6 @@ import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import java.time.Duration; -import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import java.util.stream.Stream; @@ -52,6 +52,10 @@ import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.engine.AbstractJupiterTestEngineTests; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.util.RuntimeUtils; import org.junit.platform.engine.TestExecutionResult.Status; @@ -159,29 +163,27 @@ void appliesTimeoutOnAnnotatedTestFactoryMethods() { .hasMessage("testFactoryMethod() timed out after 10 milliseconds"); } - @TestFactory + @ParameterizedTest(name = "{0}") + @ValueSource(classes = { TimeoutAnnotatedClassTestCase.class, InheritedTimeoutAnnotatedClassTestCase.class }) @DisplayName("is applied on testable methods in annotated classes") - Stream appliesTimeoutOnTestableMethodsInAnnotatedClasses() { - return Stream.of(TimeoutAnnotatedClassTestCase.class, InheritedTimeoutAnnotatedClassTestCase.class).map( - testClass -> dynamicTest(testClass.getSimpleName(), () -> { - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(testClass)) // - .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // - .build()); - - Stream.of("testMethod()", "repetition 1", "repetition 2", "testFactoryMethod()").forEach( - displayName -> { - Execution execution = findExecution(results.allEvents(), displayName); - assertThat(execution.getDuration()) // - .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // - .isLessThan(Duration.ofSeconds(1)); - assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .isInstanceOf(TimeoutException.class) // - .hasMessageEndingWith("timed out after 10000000 nanoseconds"); - }); - })); + void appliesTimeoutOnTestableMethodsInAnnotatedClasses(Class testClass) { + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(testClass)) // + .configurationParameter(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .configurationParameter(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .configurationParameter(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "42ns") // + .build()); + + assertAll(Stream.of("testMethod()", "repetition 1", "repetition 2", "testFactoryMethod()") // + .map(displayName -> () -> { + Execution execution = findExecution(results.allEvents(), displayName); + assertThat(execution.getDuration()) // + .isGreaterThanOrEqualTo(Duration.ofMillis(10)) // + .isLessThan(Duration.ofSeconds(1)); + assertThat(execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .isInstanceOf(TimeoutException.class) // + .hasMessageEndingWith("timed out after 10000000 nanoseconds"); + })); } @Test @@ -269,28 +271,32 @@ void appliesTimeoutOnAnnotatedAfterAllMethods() { .hasMessage("tearDown() timed out after 10 milliseconds"); } - @TestFactory + @ParameterizedTest(name = "{0}") + @MethodSource @DisplayName("is applied from configuration parameters by default") - Stream appliesDefaultTimeoutsFromConfigurationParameters() { - return Map.of(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "beforeAll()", // - DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "beforeEach()", // - DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "test()", // - DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "testTemplate()", // - DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "testFactory()", // - DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "afterEach()", // - DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "afterAll()" // - ).entrySet().stream().map(entry -> dynamicTest("uses " + entry.getKey() + " config param", () -> { - PlainTestCase.slowMethod = entry.getValue(); - EngineExecutionResults results = executeTests(request() // - .selectors(selectClass(PlainTestCase.class)) // - .configurationParameter(entry.getKey(), "1ns") // - .build()); - var failure = results.allEvents().executions().failed() // - .map(execution -> execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // - .findFirst(); - assertThat(failure).containsInstanceOf(TimeoutException.class); - assertThat(failure.get()).hasMessage(entry.getValue() + " timed out after 1 nanosecond"); - })); + void appliesDefaultTimeoutsFromConfigurationParameters(String propertyName, String slowMethod) { + PlainTestCase.slowMethod = slowMethod; + EngineExecutionResults results = executeTests(request() // + .selectors(selectClass(PlainTestCase.class)) // + .configurationParameter(propertyName, "1ns") // + .build()); + var failure = results.allEvents().executions().failed() // + .map(execution -> execution.getTerminationInfo().getExecutionResult().getThrowable().orElseThrow()) // + .findFirst(); + assertThat(failure).containsInstanceOf(TimeoutException.class); + assertThat(failure.orElseThrow()).hasMessage(slowMethod + " timed out after 1 nanosecond"); + } + + static Stream appliesDefaultTimeoutsFromConfigurationParameters() { + return Stream.of( // + Arguments.of(DEFAULT_BEFORE_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "beforeAll()"), // + Arguments.of(DEFAULT_BEFORE_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "beforeEach()"), // + Arguments.of(DEFAULT_TEST_METHOD_TIMEOUT_PROPERTY_NAME, "test()"), // + Arguments.of(DEFAULT_TEST_TEMPLATE_METHOD_TIMEOUT_PROPERTY_NAME, "testTemplate()"), // + Arguments.of(DEFAULT_TEST_FACTORY_METHOD_TIMEOUT_PROPERTY_NAME, "testFactory()"), // + Arguments.of(DEFAULT_AFTER_EACH_METHOD_TIMEOUT_PROPERTY_NAME, "afterEach()"), // + Arguments.of(DEFAULT_AFTER_ALL_METHOD_TIMEOUT_PROPERTY_NAME, "afterAll()") // + ); } @Test @@ -497,6 +503,7 @@ void nonTimeoutExceededInSeparateThreadOnClassLevel() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutAnnotatedTestMethodTestCase { @Test @Timeout(value = 10, unit = MILLISECONDS) @@ -518,6 +525,7 @@ Stream testFactoryMethod() throws Exception { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutAnnotatedBeforeAllMethodTestCase { @BeforeAll @Timeout(value = 10, unit = MILLISECONDS) @@ -531,6 +539,7 @@ void testMethod() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutAnnotatedBeforeEachMethodTestCase { @BeforeEach @Timeout(value = 10, unit = MILLISECONDS) @@ -544,6 +553,7 @@ void testMethod() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutAnnotatedAfterEachMethodTestCase { @Test void testMethod() { @@ -557,6 +567,7 @@ void tearDown() throws Exception { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutAnnotatedAfterAllMethodTestCase { @Test void testMethod() { @@ -595,6 +606,7 @@ Stream testFactoryMethod() throws Exception { static class InheritedTimeoutAnnotatedClassTestCase extends TimeoutAnnotatedClassTestCase { } + @SuppressWarnings("JUnitMalformedDeclaration") static class MethodWithoutInterruptedExceptionTestCase { @Test @Timeout(value = 1, unit = MILLISECONDS) @@ -603,6 +615,7 @@ void methodThatDoesNotThrowInterruptedException() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class PlainTestCase { public static String slowMethod; @@ -650,6 +663,7 @@ private static void waitForInterrupt(String methodName) throws InterruptedExcept } } + @SuppressWarnings("JUnitMalformedDeclaration") static class UnrecoverableExceptionTestCase { @Test @Timeout(value = 1, unit = NANOSECONDS) @@ -659,6 +673,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @Timeout(10) static class NonTimeoutExceedingTestCase { @Test @@ -699,6 +714,7 @@ void testMethod() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class IllegalTimeoutDurationTestCase { @Test @@ -708,6 +724,7 @@ void testMethod() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutExceedingWithInferredThreadModeTestCase { @Test @Timeout(value = 10, unit = MILLISECONDS) @@ -716,6 +733,7 @@ void testMethod() throws InterruptedException { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TimeoutExceedingSeparateThreadTestCase { @Test @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) @@ -724,6 +742,7 @@ void testMethod() throws InterruptedException { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class NonTimeoutExceedingSeparateThreadTestCase { @Test @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) @@ -731,6 +750,7 @@ void testMethod() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class UnrecoverableExceptionInSeparateThreadTestCase { @Test @Timeout(value = 100, unit = SECONDS, threadMode = SEPARATE_THREAD) @@ -739,6 +759,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class ExceptionInSeparateThreadTestCase { @Test @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) @@ -747,6 +768,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class FailedAssertionInSeparateThreadTestCase { @Test @Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD) @@ -761,6 +783,7 @@ void testJavaLangAssertion() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) static class TimeoutExceededOnClassLevelTestCase { @Test @@ -769,6 +792,7 @@ void exceptionThrown() throws InterruptedException { } } + @SuppressWarnings("JUnitMalformedDeclaration") @Timeout(value = 100, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) static class NonTimeoutExceededOnClassLevelTestCase { @Test @@ -776,6 +800,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(OrderAnnotation.class) static class OneTestStuckForeverAndTheOthersNotTestCase { @@ -799,6 +824,7 @@ void testOne() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class MixedSameThreadAndSeparateThreadTestCase { @Test @Timeout(value = 10, unit = MILLISECONDS, threadMode = SEPARATE_THREAD) @@ -819,6 +845,7 @@ void testTwo() throws InterruptedException { } } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(OrderAnnotation.class) static class OneTestStuckForeverAndTheOthersInSameThreadNotTestCase { diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java similarity index 77% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java index ba4536af5594..234a2c19e63e 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TimeoutInvocationFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout.ThreadMode; +import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Store; @@ -30,6 +31,11 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +// org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'store'. +// Mockito cannot mock this class: class org.junit.jupiter.engine.execution.NamespaceAwareStore. +// You are seeing this disclaimer because Mockito is configured to create inlined mocks. +// Byte Buddy could not instrument all classes within the mock's type hierarchy. +@DisabledIf(value = "runningInEclipse", disabledReason = "Mockito cannot create a spy for NamespaceAwareStore using the inline MockMaker in Eclipse IDE") @DisplayName("TimeoutInvocationFactory") @ExtendWith(MockitoExtension.class) class TimeoutInvocationFactoryTests { @@ -37,16 +43,21 @@ class TimeoutInvocationFactoryTests { @Spy private final Store store = new NamespaceAwareStore(new NamespacedHierarchicalStore<>(null), ExtensionContext.Namespace.create(TimeoutInvocationFactoryTests.class)); + @Mock private Invocation invocation; + @Mock private TimeoutDuration timeoutDuration; + private TimeoutInvocationFactory timeoutInvocationFactory; + private TimeoutInvocationParameters parameters; @BeforeEach void setUp() { - parameters = new TimeoutInvocationParameters<>(invocation, timeoutDuration, () -> "description"); + parameters = new TimeoutInvocationParameters<>(invocation, timeoutDuration, () -> "description", + PreInterruptCallbackInvocation.NOOP); timeoutInvocationFactory = new TimeoutInvocationFactory(store); } @@ -86,4 +97,13 @@ void shouldCreateTimeoutInvocationForSeparateThreadTimeoutThreadMode() { assertThat(invocation).isInstanceOf(SeparateThreadTimeoutInvocation.class); } + /** + * Determine if the current code is running in the Eclipse IDE. + *

    Copied from {@code org.springframework.core.testfixture.ide.IdeUtils}. + */ + static boolean runningInEclipse() { + return StackWalker.getInstance().walk( + stream -> stream.anyMatch(stackFrame -> stackFrame.getClassName().startsWith("org.eclipse.jdt"))); + } + } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java similarity index 95% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java index ba60a5f27ac5..d32cb1bfe3eb 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AlwaysDisabledCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java index 2dd26dfcda19..9683666a67d4 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/AnotherAlwaysDisabledCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java index a4336b4b5d14..bb50d774a31c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/sub/SystemPropertyCondition.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,7 @@ package org.junit.jupiter.engine.extension.sub; -import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java similarity index 93% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java index 77e807247f7b..07f895044a7b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/subpackage/SuperClassWithPackagePrivateLifecycleMethodInDifferentPackageTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java similarity index 94% rename from junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java index 17a195eea29a..846f8886fc8c 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/engine/support/OpenTest4JAndJUnit4AwareThrowableCollectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -24,6 +24,7 @@ import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.api.function.Executable; import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.util.ReflectionUtils; /** @@ -42,10 +43,10 @@ void simulateJUnit4NotInTheClasspath(LogRecordListener listener) throws Throwabl // Ensure that our custom ClassLoader actually throws a ClassNotFoundException // when attempting to load the AssumptionViolatedException class. assertThrows(ClassNotFoundException.class, - () -> ReflectionUtils.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); + () -> ReflectionSupport.tryToLoadClass(AssumptionViolatedException.class.getName()).get()); Class clazz = classLoader.loadClass(OpenTest4JAndJUnit4AwareThrowableCollector.class.getName()); - assertNotNull(ReflectionUtils.newInstance(clazz)); + assertNotNull(ReflectionSupport.newInstance(clazz)); // @formatter:off assertThat(listener.stream(Level.FINE).map(LogRecord::getMessage).findFirst().orElse("")) diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java similarity index 95% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java index 80cca83a0b5c..1dfe99b4cafe 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/JupiterMigrationSupportTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java similarity index 97% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java index 0c9487556d82..532703006296 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreAnnotationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java similarity index 95% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java index f427d7d6769d..d1c3556cec40 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/conditions/IgnoreConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -106,6 +106,7 @@ private EngineExecutionResults executeTestsForClass(Class testClass) { // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(IgnoreCondition.class) @Ignore static class IgnoredClassWithDefaultMessageTestCase { @@ -116,6 +117,7 @@ void ignoredBecauseClassIsIgnored() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(IgnoreCondition.class) @Ignore("Ignored Class") static class IgnoredClassWithCustomMessageTestCase { @@ -126,6 +128,7 @@ void ignoredBecauseClassIsIgnored() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(IgnoreCondition.class) static class IgnoredMethodsTestCase { diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java similarity index 97% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java index 3457cf3ed04c..9890182b4a40 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/AbstractTestRuleAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java similarity index 97% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java index 14ef7fb260e8..ba149cdf0e1b 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/EnableRuleMigrationSupportWithBothRuleTypesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java similarity index 96% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java index be8df6202fd0..2ee4f9155881 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExpectedExceptionSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -76,6 +76,7 @@ private Events executeTestsForClass(Class testClass) { return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExpectedExceptionSupport.class) static class ExpectedExceptionTestCase { @@ -107,6 +108,7 @@ void correctExceptionExpectedThrown() { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(ExpectedExceptionSupport.class) static class ExpectedExceptionSupportWithoutExpectedExceptionRuleTestCase { diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java similarity index 97% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java index 9f9db6def7b3..c71f63ac9178 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForDifferentDeclaredReturnTypesRulesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java similarity index 94% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java index e02d4d774ccc..c4fc1b66a0d3 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMixedMethodAndFieldRulesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -45,11 +45,13 @@ static void clear() { @Rule public ExternalResource fieldRule2 = new MyExternalResource("fieldRule2"); + @SuppressWarnings("JUnitMalformedDeclaration") @Rule ExternalResource methodRule1() { return new MyExternalResource("methodRule1"); } + @SuppressWarnings("JUnitMalformedDeclaration") @Rule ExternalResource methodRule2() { return new MyExternalResource("methodRule2"); diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java similarity index 96% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java index 6cc63fd9828e..f2ba8354f6cc 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleFieldRulesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java similarity index 97% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java index 593c02cff54c..e5ad795f43c0 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForMultipleMethodRulesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java similarity index 94% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java index 8495213209e7..c58690716548 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportForTemporaryFolderFieldTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java similarity index 88% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java index 230e6e0b59b4..71b645e6a9a9 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceSupportWithInheritanceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java similarity index 94% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java index 474135bd0fca..18c70f02eddc 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/ExternalResourceWithoutAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java similarity index 90% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java index 57fc525000a6..ae15007c7462 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/FailAfterAllHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java similarity index 95% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java index 558a9c490336..1a022944706b 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/LauncherBasedEnableRuleMigrationSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -52,6 +52,7 @@ private Events executeTestsForClass(Class testClass) { return EngineTestKit.execute("junit-jupiter", request().selectors(selectClass(testClass)).build()).testEvents(); } + @SuppressWarnings("JUnitMalformedDeclaration") @EnableRuleMigrationSupport static class EnableRuleMigrationSupportWithBothRuleTypesTestCase { @@ -93,6 +94,7 @@ void beforeMethodOfBothRule2WasExecuted() { } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(VerifierSupport.class) static class VerifierSupportForErrorCollectorTestCase { diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java similarity index 96% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java index 50f5158ded28..42a292c7e860 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/VerifierSupportForMixedMethodAndFieldRulesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java similarity index 95% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java index b2a7335794b6..ec4c7871fdb3 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierFieldTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java similarity index 95% rename from junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java index 12ff43594287..77d89f7bddb7 100644 --- a/junit-jupiter-migrationsupport/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/migrationsupport/rules/WrongExtendWithForVerifierMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java similarity index 73% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java index 69db68af81ba..aa36f907aa25 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestExtensionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,12 +15,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.ParameterizedTestExtension.METHOD_CONTEXT_KEY; import static org.junit.jupiter.params.ParameterizedTestExtension.arguments; import java.io.FileNotFoundException; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.util.Arrays; +import java.nio.file.Path; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -32,7 +34,9 @@ import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExecutableInvoker; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.MediaType; import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.api.function.ThrowingConsumer; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.engine.execution.NamespaceAwareStore; import org.junit.jupiter.params.provider.Arguments; @@ -40,6 +44,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.support.store.NamespacedHierarchicalStore; /** @@ -140,6 +145,25 @@ void throwsExceptionWhenParameterizedTestIsNotInvokedAtLeastOnce() { "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest"); } + @Test + void doesNotThrowExceptionWhenParametrizedTestDoesNotRequireArguments() { + var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseAllowNoArgumentsMethod()); + + var stream = this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext); + // cause the stream to be evaluated + stream.toArray(); + stream.close(); + } + + @Test + void throwsExceptionWhenParameterizedTestHasNoArgumentsSource() { + var extensionContext = getExtensionContextReturningSingleMethod(new TestCaseWithNoArgumentsSource()); + + assertThrows(PreconditionViolationException.class, + () -> this.parameterizedTestExtension.provideTestTemplateInvocationContexts(extensionContext), + "Configuration error: You must configure at least one arguments source for this @ParameterizedTest"); + } + @Test void throwsExceptionWhenArgumentsProviderIsNotStatic() { var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( @@ -150,11 +174,14 @@ void throwsExceptionWhenArgumentsProviderIsNotStatic() { var exception = assertThrows(JUnitException.class, stream::toArray); - assertArgumentsProviderInstantiationException(exception, NonStaticArgumentsProvider.class); + assertThat(exception) // + .hasMessage(String.format( + "The ArgumentsProvider [%s] must be either a top-level class or a static nested class", + NonStaticArgumentsProvider.class.getName())); } @Test - void throwsExceptionWhenArgumentsProviderDoesNotContainNoArgumentConstructor() { + void throwsExceptionWhenArgumentsProviderDoesNotContainUnambiguousConstructor() { var extensionContextWithAnnotatedTestMethod = getExtensionContextReturningSingleMethod( new MissingNoArgumentsConstructorArgumentsProviderTestCase()); @@ -163,15 +190,11 @@ void throwsExceptionWhenArgumentsProviderDoesNotContainNoArgumentConstructor() { var exception = assertThrows(JUnitException.class, stream::toArray); - assertArgumentsProviderInstantiationException(exception, MissingNoArgumentsConstructorArgumentsProvider.class); - } - - private void assertArgumentsProviderInstantiationException(JUnitException exception, Class clazz) { - assertThat(exception).hasMessage( - String.format("Failed to find a no-argument constructor for ArgumentsProvider [%s]. " - + "Please ensure that a no-argument constructor exists and " - + "that the class is either a top-level class or a static nested class", - clazz.getName())); + String className = AmbiguousConstructorArgumentsProvider.class.getName(); + assertThat(exception) // + .hasMessage(String.format("Failed to find constructor for ArgumentsProvider [%s]. " + + "Please ensure that a no-argument or a single constructor exists.", + className)); } private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase) { @@ -181,11 +204,8 @@ private ExtensionContext getExtensionContextReturningSingleMethod(Object testCas private ExtensionContext getExtensionContextReturningSingleMethod(Object testCase, Function> configurationSupplier) { - // @formatter:off - var optional = Arrays.stream(testCase.getClass().getDeclaredMethods()) - .filter(method -> method.getName().equals("method")) - .findFirst(); - // @formatter:on + var method = ReflectionUtils.findMethods(testCase.getClass(), + it -> "method".equals(it.getName())).stream().findFirst(); return new ExtensionContext() { @@ -193,7 +213,7 @@ private ExtensionContext getExtensionContextReturningSingleMethod(Object testCas @Override public Optional getTestMethod() { - return optional; + return method; } @Override @@ -265,9 +285,21 @@ public Optional getConfigurationParameter(String key, Function public void publishReportEntry(Map map) { } + @Override + public void publishFile(String fileName, MediaType mediaType, ThrowingConsumer action) { + } + + @Override + public void publishDirectory(String name, ThrowingConsumer action) { + } + @Override public Store getStore(Namespace namespace) { - return new NamespaceAwareStore(store, namespace); + var store = new NamespaceAwareStore(this.store, namespace); + method // + .map(it -> new ParameterizedTestMethodContext(it, it.getAnnotation(ParameterizedTest.class))) // + .ifPresent(ctx -> store.put(METHOD_CONTEXT_KEY, ctx)); + return store; } @Override @@ -277,7 +309,17 @@ public ExecutionMode getExecutionMode() { @Override public ExecutableInvoker getExecutableInvoker() { - return null; + return new ExecutableInvoker() { + @Override + public Object invoke(Method method, Object target) { + return null; + } + + @Override + public T invoke(Constructor constructor, Object outerInstance) { + return ReflectionUtils.newInstance(constructor); + } + }; } }; } @@ -294,10 +336,35 @@ void method() { static class TestCaseWithAnnotatedMethod { @ParameterizedTest + @ArgumentsSource(ZeroArgumentsProvider.class) + void method() { + } + } + + static class TestCaseAllowNoArgumentsMethod { + + @ParameterizedTest(allowZeroInvocations = true) + @ArgumentsSource(ZeroArgumentsProvider.class) + void method() { + } + } + + static class TestCaseWithNoArgumentsSource { + + @ParameterizedTest(allowZeroInvocations = true) + @SuppressWarnings("JUnitMalformedDeclaration") void method() { } } + static class ZeroArgumentsProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.empty(); + } + } + static class ArgumentsProviderWithCloseHandlerTestCase { @ParameterizedTest @@ -334,7 +401,7 @@ public Stream provideArguments(ExtensionContext context) { static class MissingNoArgumentsConstructorArgumentsProviderTestCase { @ParameterizedTest - @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) + @ArgumentsSource(AmbiguousConstructorArgumentsProvider.class) void method() { } } @@ -342,7 +409,7 @@ void method() { static class EmptyDisplayNameProviderTestCase { @ParameterizedTest(name = "") - @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) + @ArgumentsSource(AmbiguousConstructorArgumentsProvider.class) void method() { } } @@ -350,14 +417,17 @@ void method() { static class DefaultDisplayNameProviderTestCase { @ParameterizedTest - @ArgumentsSource(MissingNoArgumentsConstructorArgumentsProvider.class) + @ArgumentsSource(AmbiguousConstructorArgumentsProvider.class) void method() { } } - static class MissingNoArgumentsConstructorArgumentsProvider implements ArgumentsProvider { + static class AmbiguousConstructorArgumentsProvider implements ArgumentsProvider { + + AmbiguousConstructorArgumentsProvider(String parameter) { + } - MissingNoArgumentsConstructorArgumentsProvider(String parameter) { + AmbiguousConstructorArgumentsProvider(int parameter) { } @Override diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java similarity index 83% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 6320273e3490..eabe6470ec83 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.jupiter.params; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -30,6 +31,7 @@ import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; +import static org.junit.platform.testkit.engine.EventConditions.engine; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; @@ -40,8 +42,8 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -84,6 +86,8 @@ import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.jupiter.params.ParameterizedTestIntegrationTests.RepeatableSourcesTestCase.Action; import org.junit.jupiter.params.aggregator.AggregateWith; @@ -112,6 +116,7 @@ import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Event; +import org.junit.platform.testkit.engine.EventConditions; import org.opentest4j.TestAbortedException; /** @@ -401,20 +406,20 @@ void executesLifecycleMethods() { assertThat(LifecycleTestCase.lifecycleEvents).containsExactly( "beforeAll:ParameterizedTestIntegrationTests$LifecycleTestCase", "providerMethod", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "constructor:[1] argument=foo", "beforeEach:[1] argument=foo", testMethods.get(0) + ":[1] argument=foo", "afterEach:[1] argument=foo", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "constructor:[2] argument=bar", "beforeEach:[2] argument=bar", testMethods.get(0) + ":[2] argument=bar", "afterEach:[2] argument=bar", "providerMethod", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "constructor:[1] argument=foo", "beforeEach:[1] argument=foo", testMethods.get(1) + ":[1] argument=foo", "afterEach:[1] argument=foo", - "constructor:ParameterizedTestIntegrationTests$LifecycleTestCase", + "constructor:[2] argument=bar", "beforeEach:[2] argument=bar", testMethods.get(1) + ":[2] argument=bar", "afterEach:[2] argument=bar", @@ -444,6 +449,33 @@ void displayNamePatternFromConfiguration() { .haveExactly(1, event(displayName("2"), started())); } + @Test + void failsWhenArgumentsRequiredButNoneProvided() { + var result = execute(ZeroArgumentsTestCase.class, "testThatRequiresArguments", String.class); + result.containerEvents().assertThatEvents().haveExactly(1, event(finishedWithFailure(message( + "Configuration error: You must configure at least one set of arguments for this @ParameterizedTest")))); + } + + @Test + void doesNotFailWhenArgumentsAreNotRequiredAndNoneProvided() { + var result = execute(ZeroArgumentsTestCase.class, "testThatDoesNotRequireArguments", String.class); + result.allEvents().assertEventsMatchExactly( // + event(engine(), started()), event(container(ZeroArgumentsTestCase.class), started()), + event(container("testThatDoesNotRequireArguments"), started()), + event(container("testThatDoesNotRequireArguments"), finishedSuccessfully()), + event(container(ZeroArgumentsTestCase.class), finishedSuccessfully()), + event(engine(), finishedSuccessfully())); + } + + @Test + void failsWhenNoArgumentsSourceIsDeclared() { + var result = execute(ZeroArgumentsTestCase.class, "testThatHasNoArgumentsSource", String.class); + result.containerEvents().assertThatEvents() // + .haveExactly(1, // + event(displayName("testThatHasNoArgumentsSource(String)"), finishedWithFailure(message( + "Configuration error: You must configure at least one arguments source for this @ParameterizedTest")))); + } + private EngineExecutionResults execute(DiscoverySelector... selectors) { return EngineTestKit.engine(new JupiterTestEngine()).selectors(selectors).execute(); } @@ -1091,11 +1123,80 @@ private EngineExecutionResults execute(String methodName, Class... methodPara } @Nested - class RepeatableSourcesIntegrationTests { + class UnusedArgumentsWithStrictArgumentsCountIntegrationTests { + @Test + void failsWithArgumentsSourceProvidingUnusedArguments() { + var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, + "testWithTwoUnusedStringArgumentsProvider", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(EventConditions.finishedWithFailure(message(String.format( + "Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.%nNote: the provided arguments are [foo, unused1]"))))); + } + + @Test + void failsWithMethodSourceProvidingUnusedArguments() { + var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, + "testWithMethodSourceProvidingUnusedArguments", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(EventConditions.finishedWithFailure(message(String.format( + "Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.%nNote: the provided arguments are [foo, unused1]"))))); + } + + @Test + void failsWithCsvSourceUnusedArgumentsAndStrictArgumentCountValidationAnnotationAttribute() { + var results = execute(ArgumentCountValidationMode.NONE, UnusedArgumentsTestCase.class, + "testWithStrictArgumentCountValidation", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(EventConditions.finishedWithFailure(message(String.format( + "Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.%nNote: the provided arguments are [foo, unused1]"))))); + } + + @Test + void failsWithCsvSourceUnusedArgumentsButExecutesRemainingArgumentsWhereThereIsNoUnusedArgument() { + var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, + "testWithCsvSourceContainingDifferentNumbersOfArguments", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(EventConditions.finishedWithFailure(message(String.format( + "Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.%nNote: the provided arguments are [foo, unused1]"))))) // + .haveExactly(1, + event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); + } @Test - void executesWithRepeatableCsvFileSource() { - var results = execute("testWithRepeatableCsvFileSource", String.class, String.class); + void executesWithCsvSourceUnusedArgumentsAndArgumentCountValidationAnnotationAttribute() { + var results = execute(ArgumentCountValidationMode.NONE, UnusedArgumentsTestCase.class, + "testWithNoneArgumentCountValidation", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, + event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))); + } + + @Test + void executesWithMethodSourceProvidingUnusedArguments() { + var results = execute(ArgumentCountValidationMode.STRICT, RepeatableSourcesTestCase.class, + "testWithRepeatableCsvSource", String.class); + results.allEvents().assertThatEvents() // + .haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) // + .haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b")))); + } + + private EngineExecutionResults execute(ArgumentCountValidationMode configurationValue, Class javaClass, + String methodName, Class... methodParameterTypes) { + return EngineTestKit.engine(new JupiterTestEngine()) // + .selectors(selectMethod(javaClass, methodName, methodParameterTypes)) // + .configurationParameter(ArgumentCountValidator.ARGUMENT_COUNT_VALIDATION_KEY, + configurationValue.name().toLowerCase()) // + .execute(); + } + } + + @Nested + class RepeatableSourcesIntegrationTests { + + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableCsvFileSource", "testWithRepeatableCsvFileSourceAsMetaAnnotation" }) + void executesWithRepeatableCsvFileSource(String methodName) { + var results = execute(methodName, String.class, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] column1=foo, column2=1"), finishedWithFailure(message("foo 1")))) // @@ -1103,17 +1204,19 @@ void executesWithRepeatableCsvFileSource() { finishedWithFailure(message("apple 1")))); } - @Test - void executesWithRepeatableCsvSource() { - var results = execute("testWithRepeatableCsvSource", String.class); + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableCsvSource", "testWithRepeatableCsvSourceAsMetaAnnotation" }) + void executesWithRepeatableCsvSource(String methodName) { + var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) // .haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b")))); } - @Test - void executesWithRepeatableMethodSource() { - var results = execute("testWithRepeatableMethodSource", String.class); + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableMethodSource", "testWithRepeatableMethodSourceAsMetaAnnotation" }) + void executesWithRepeatableMethodSource(String methodName) { + var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // @@ -1121,27 +1224,30 @@ void executesWithRepeatableMethodSource() { event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } - @Test - void executesWithRepeatableEnumSource() { - var results = execute("testWithRepeatableEnumSource", Action.class); + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableEnumSource", "testWithRepeatableEnumSourceAsMetaAnnotation" }) + void executesWithRepeatableEnumSource(String methodName) { + var results = execute(methodName, Action.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=FOO"), finishedWithFailure(message("FOO")))) // .haveExactly(1, event(test(), displayName("[2] argument=BAR"), finishedWithFailure(message("BAR")))); } - @Test - void executesWithRepeatableValueSource() { - var results = execute("testWithRepeatableValueSource", String.class); + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableValueSource", "testWithRepeatableValueSourceAsMetaAnnotation" }) + void executesWithRepeatableValueSource(String methodName) { + var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))); } - @Test - void executesWithRepeatableFieldSource() { - var results = execute("testWithRepeatableFieldSource", String.class); + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableFieldSource", "testWithRepeatableFieldSourceAsMetaAnnotation" }) + void executesWithRepeatableFieldSource(String methodName) { + var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=some"), finishedWithFailure(message("some")))) // @@ -1149,9 +1255,11 @@ void executesWithRepeatableFieldSource() { event(test(), displayName("[2] argument=other"), finishedWithFailure(message("other")))); } - @Test - void executesWithRepeatableArgumentsSource() { - var results = execute("testWithRepeatableArgumentsSource", String.class); + @ParameterizedTest + @ValueSource(strings = { "testWithRepeatableArgumentsSource", + "testWithRepeatableArgumentsSourceAsMetaAnnotation" }) + void executesWithRepeatableArgumentsSource(String methodName) { + var results = execute(methodName, String.class); results.allEvents().assertThatEvents() // .haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo")))) // .haveExactly(1, event(test(), displayName("[2] argument=bar"), finishedWithFailure(message("bar")))) // @@ -1206,6 +1314,31 @@ void executesTwoIterationsBasedOnIterationAndUniqueIdSelector() { .haveExactly(1, event(test(), displayName("[3] argument=5"), finishedWithFailure())); } + @Nested + class SpiParameterInjectionIntegrationTests { + + @Test + void injectsParametersIntoArgumentsProviderConstructor() { + execute(SpiParameterInjectionTestCase.class, "argumentsProviderWithConstructorParameter", String.class) // + .testEvents() // + .assertStatistics(it -> it.succeeded(1)); + } + + @Test + void injectsParametersIntoArgumentConverterConstructor() { + execute(SpiParameterInjectionTestCase.class, "argumentConverterWithConstructorParameter", String.class) // + .testEvents() // + .assertStatistics(it -> it.succeeded(1)); + } + + @Test + void injectsParametersIntoArgumentsAggregatorConstructor() { + execute(SpiParameterInjectionTestCase.class, "argumentsAggregatorWithConstructorParameter", String.class) // + .testEvents() // + .assertStatistics(it -> it.succeeded(1)); + } + } + // ------------------------------------------------------------------------- static class TestCase { @@ -1283,13 +1416,13 @@ void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvSource(String argu } @ParameterizedTest - @CsvFileSource(resources = "/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = false) + @CsvFileSource(resources = "provider/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = false) void testWithIgnoreLeadingAndTrailingWhitespaceSetToFalseForCsvFileSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } @ParameterizedTest - @CsvFileSource(resources = "/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = true) + @CsvFileSource(resources = "provider/leading-trailing-spaces.csv", ignoreLeadingAndTrailingWhitespace = true) void testWithIgnoreLeadingAndTrailingWhitespaceSetToTrueForCsvFileSource(String argument1, String argument2) { fail("arguments: '" + argument1 + "', '" + argument2 + "'"); } @@ -1307,6 +1440,7 @@ void testWithThreeIterations(int argument) { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class NullSourceTestCase { @ParameterizedTest @@ -1342,6 +1476,7 @@ void testWithNullSourceForPrimitive(int argument) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class EmptySourceTestCase { @ParameterizedTest @@ -1497,6 +1632,7 @@ void testWithEmptySourceForUnsupportedReferenceType(Integer argument) { } + @SuppressWarnings("JUnitMalformedDeclaration") static class NullAndEmptySourceTestCase { @ParameterizedTest @@ -1538,11 +1674,12 @@ void testWithNullAndEmptySourceForTwoDimensionalStringArray(String[][] argument) } + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(OrderAnnotation.class) static class MethodSourceTestCase { @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) + @Retention(RUNTIME) @ParameterizedTest(name = "{arguments}") @MethodSource @interface MethodSourceTest { @@ -1767,7 +1904,7 @@ private static Stream test() { static class FieldSourceTestCase { @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) + @Retention(RUNTIME) @ParameterizedTest(name = "{arguments}") @FieldSource @interface FieldSourceTest { @@ -1969,6 +2106,23 @@ void testWithFieldSourceProvidingUnusedArguments(String argument) { static Supplier> unusedArgumentsProviderField = // () -> Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2")); + @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT) + @CsvSource({ "foo, unused1" }) + void testWithStrictArgumentCountValidation(String argument) { + fail(argument); + } + + @ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.NONE) + @CsvSource({ "foo, unused1" }) + void testWithNoneArgumentCountValidation(String argument) { + fail(argument); + } + + @ParameterizedTest + @CsvSource({ "foo, unused1", "bar" }) + void testWithCsvSourceContainingDifferentNumbersOfArguments(String argument) { + fail(argument); + } } static class LifecycleTestCase { @@ -2035,6 +2189,18 @@ void testWithRepeatableCsvFileSource(String column1, String column2) { fail("%s %s".formatted(column1, column2)); } + @CsvFileSource(resources = "two-column.csv") + @CsvFileSource(resources = "two-column-with-headers.csv", delimiter = '|', useHeadersInDisplayName = true, nullValues = "NIL") + @Retention(RUNTIME) + @interface TwoCsvFileSources { + } + + @ParameterizedTest + @TwoCsvFileSources + void testWithRepeatableCsvFileSourceAsMetaAnnotation(String column1, String column2) { + fail("%s %s".formatted(column1, column2)); + } + @ParameterizedTest @CsvSource({ "a" }) @CsvSource({ "b" }) @@ -2042,6 +2208,18 @@ void testWithRepeatableCsvSource(String argument) { fail(argument); } + @CsvSource({ "a" }) + @CsvSource({ "b" }) + @Retention(RUNTIME) + @interface TwoCsvSources { + } + + @ParameterizedTest + @TwoCsvSources + void testWithRepeatableCsvSourceAsMetaAnnotation(String argument) { + fail(argument); + } + @ParameterizedTest @EnumSource(SmartAction.class) @EnumSource(QuickAction.class) @@ -2049,6 +2227,18 @@ void testWithRepeatableEnumSource(Action argument) { fail(argument.toString()); } + @EnumSource(SmartAction.class) + @EnumSource(QuickAction.class) + @Retention(RUNTIME) + @interface TwoEnumSources { + } + + @ParameterizedTest + @TwoEnumSources + void testWithRepeatableEnumSourceAsMetaAnnotation(Action argument) { + fail(argument.toString()); + } + interface Action { } @@ -2067,6 +2257,19 @@ void testWithRepeatableMethodSource(String argument) { fail(argument); } + @MethodSource("someArgumentsMethodSource") + @MethodSource("otherArgumentsMethodSource") + @Retention(RUNTIME) + @interface TwoMethodSources { + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ParameterizedTest + @TwoMethodSources + void testWithRepeatableMethodSourceAsMetaAnnotation(String argument) { + fail(argument); + } + public static Stream someArgumentsMethodSource() { return Stream.of(Arguments.of("some")); } @@ -2082,6 +2285,18 @@ void testWithRepeatableFieldSource(String argument) { fail(argument); } + @FieldSource("someArgumentsContainer") + @FieldSource("otherArgumentsContainer") + @Retention(RUNTIME) + @interface TwoFieldSources { + } + + @ParameterizedTest + @TwoFieldSources + void testWithRepeatableFieldSourceAsMetaAnnotation(String argument) { + fail(argument); + } + static List someArgumentsContainer = List.of("some"); static List otherArgumentsContainer = List.of("other"); @@ -2092,6 +2307,18 @@ void testWithRepeatableValueSource(String argument) { fail(argument); } + @ValueSource(strings = "foo") + @ValueSource(strings = "bar") + @Retention(RUNTIME) + @interface TwoValueSources { + } + + @ParameterizedTest + @TwoValueSources + void testWithRepeatableValueSourceAsMetaAnnotation(String argument) { + fail(argument); + } + @ParameterizedTest @ValueSource(strings = "foo") @ValueSource(strings = "foo") @@ -2117,6 +2344,108 @@ void testWithDifferentRepeatableAnnotations(String argument) { void testWithRepeatableArgumentsSource(String argument) { fail(argument); } + + @ArgumentsSource(TwoSingleStringArgumentsProvider.class) + @ArgumentsSource(TwoUnusedStringArgumentsProvider.class) + @Retention(RUNTIME) + @interface TwoArgumentsSources { + } + + @ParameterizedTest + @TwoArgumentsSources + void testWithRepeatableArgumentsSourceAsMetaAnnotation(String argument) { + fail(argument); + } + } + + static class SpiParameterInjectionTestCase { + + @RegisterExtension + static final ParameterResolver spiParameterResolver = new ParameterResolver() { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.getDeclaringExecutable() instanceof Constructor // + && String.class.equals(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return "resolved value"; + } + }; + + @ParameterizedTest + @ArgumentsSource(ArgumentsProviderWithConstructorParameter.class) + void argumentsProviderWithConstructorParameter(String argument) { + assertEquals("resolved value", argument); + } + + @ParameterizedTest + @ValueSource(strings = "value") + void argumentConverterWithConstructorParameter( + @ConvertWith(ArgumentConverterWithConstructorParameter.class) String argument) { + assertEquals("resolved value", argument); + } + + @ParameterizedTest + @ValueSource(strings = "value") + void argumentsAggregatorWithConstructorParameter( + @AggregateWith(ArgumentsAggregatorWithConstructorParameter.class) String argument) { + assertEquals("resolved value", argument); + } + + record ArgumentsProviderWithConstructorParameter(String value) implements ArgumentsProvider { + + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of(arguments(value)); + } + } + + record ArgumentConverterWithConstructorParameter(String value) implements ArgumentConverter { + + @Override + public Object convert(Object source, ParameterContext context) throws ArgumentConversionException { + return value; + } + } + + record ArgumentsAggregatorWithConstructorParameter(String value) implements ArgumentsAggregator { + + @Override + public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) + throws ArgumentsAggregationException { + return value; + } + } + } + + static class ZeroArgumentsTestCase { + + @ParameterizedTest + @MethodSource("zeroArgumentsProvider") + void testThatRequiresArguments(String argument) { + fail("This test should not be executed, because no arguments are provided."); + } + + @ParameterizedTest(allowZeroInvocations = true) + @MethodSource("zeroArgumentsProvider") + void testThatDoesNotRequireArguments(String argument) { + fail("This test should not be executed, because no arguments are provided."); + } + + @ParameterizedTest(allowZeroInvocations = true) + @SuppressWarnings("JUnitMalformedDeclaration") + void testThatHasNoArgumentsSource(String argument) { + fail("This test should not be executed, because no arguments source is declared."); + } + + public static Stream zeroArgumentsProvider() { + return Stream.empty(); + } } private static class TwoSingleStringArgumentsProvider implements ArgumentsProvider { diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java new file mode 100644 index 000000000000..7272b084a52d --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestMethodContextTests.java @@ -0,0 +1,87 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.CsvToPerson; +import org.junit.jupiter.params.aggregator.AggregatorIntegrationTests.Person; +import org.junit.jupiter.params.aggregator.ArgumentsAccessor; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.util.ReflectionUtils; + +/** + * Unit tests for {@link ParameterizedTestMethodContext}. + * + * @since 5.2 + */ +class ParameterizedTestMethodContextTests { + + @ParameterizedTest + @ValueSource(strings = { "onePrimitive", "twoPrimitives", "twoAggregators", "twoAggregatorsWithTestInfoAtTheEnd", + "mixedMode" }) + void validSignatures(String methodName) { + assertTrue(createMethodContext(ValidTestCase.class, methodName).hasPotentiallyValidSignature()); + } + + @ParameterizedTest + @ValueSource(strings = { "twoAggregatorsWithPrimitiveInTheMiddle", "twoAggregatorsWithTestInfoInTheMiddle" }) + void invalidSignatures(String methodName) { + assertFalse(createMethodContext(InvalidTestCase.class, methodName).hasPotentiallyValidSignature()); + } + + private ParameterizedTestMethodContext createMethodContext(Class testClass, String methodName) { + var method = ReflectionUtils.findMethods(testClass, m -> m.getName().equals(methodName)).getFirst(); + return new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class)); + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class ValidTestCase { + + @ParameterizedTest + void onePrimitive(int num) { + } + + @ParameterizedTest + void twoPrimitives(int num1, int num2) { + } + + @ParameterizedTest + void twoAggregators(@CsvToPerson Person person, ArgumentsAccessor arguments) { + } + + @ParameterizedTest + void twoAggregatorsWithTestInfoAtTheEnd(@CsvToPerson Person person1, @CsvToPerson Person person2, + TestInfo testInfo) { + } + + @ParameterizedTest + void mixedMode(int num1, int num2, ArgumentsAccessor arguments1, ArgumentsAccessor arguments2, + @CsvToPerson Person person1, @CsvToPerson Person person2, TestInfo testInfo1, TestInfo testInfo2) { + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + static class InvalidTestCase { + + @ParameterizedTest + void twoAggregatorsWithPrimitiveInTheMiddle(@CsvToPerson Person person1, int num, @CsvToPerson Person person2) { + } + + @ParameterizedTest + void twoAggregatorsWithTestInfoInTheMiddle(@CsvToPerson Person person1, TestInfo testInfo, + @CsvToPerson Person person2) { + } + } + +} diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java similarity index 96% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java index bba252b4fbd0..fc07dae767a9 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestNameFormatterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -47,11 +47,12 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.platform.commons.JUnitException; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * @since 5.0 */ +@SuppressWarnings("ALL") class ParameterizedTestNameFormatterTests { private final Locale originalLocale = Locale.getDefault(); @@ -330,8 +331,8 @@ private static ParameterizedTestNameFormatter formatter(String pattern, String d } private static ParameterizedTestNameFormatter formatter(String pattern, String displayName, Method method) { - return new ParameterizedTestNameFormatter(pattern, displayName, new ParameterizedTestMethodContext(method), - 512); + var context = new ParameterizedTestMethodContext(method, method.getAnnotation(ParameterizedTest.class)); + return new ParameterizedTestNameFormatter(pattern, displayName, context, 512); } private static String format(ParameterizedTestNameFormatter formatter, int invocationIndex, Arguments arguments) { @@ -357,19 +358,22 @@ public String toString() { private static class ParameterizedTestCases { static Method getMethod(String methodName, Class... parameterTypes) { - return ReflectionUtils.findMethod(ParameterizedTestCases.class, methodName, parameterTypes).orElseThrow(); + return ReflectionSupport.findMethod(ParameterizedTestCases.class, methodName, parameterTypes).orElseThrow(); } @SuppressWarnings("unused") + @ParameterizedTest void parameterizedTest(int someNumber, String someString, Object[] someArray) { } @SuppressWarnings("unused") + @ParameterizedTest void parameterizedTestWithAggregator(int someNumber, @AggregateWith(CustomAggregator.class) String someAggregatedString) { } @SuppressWarnings("unused") + @ParameterizedTest void processFruits(String fruit1, String fruit2) { } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java similarity index 95% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java index 3d16c471d0d2..a52d27408719 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java similarity index 99% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java index a6601c9ec05e..90bfcb7b367b 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/AggregatorIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -410,6 +410,7 @@ static class CountingTestCase { static final List output = new ArrayList<>(); + @SuppressWarnings("JUnitMalformedDeclaration") @ParameterizedTest @ValueSource(ints = { 1, 2, 3 }) void testWithCountingConverterAggregator(@ConvertWith(InstanceCountingConverter.class) int i, diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java similarity index 96% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java index 1e9b95bb0079..22d1ce685a67 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/aggregator/DefaultArgumentsAccessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -25,7 +25,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * Unit tests for {@link DefaultArgumentsAccessor}. @@ -173,7 +173,7 @@ private static DefaultArgumentsAccessor defaultArgumentsAccessor(int invocationI } private static ParameterContext parameterContext() { - Method declaringExecutable = ReflectionUtils.findMethod(DefaultArgumentsAccessorTests.class, "foo").get(); + Method declaringExecutable = ReflectionSupport.findMethod(DefaultArgumentsAccessorTests.class, "foo").get(); ParameterContext parameterContext = mock(); when(parameterContext.getDeclaringExecutable()).thenReturn(declaringExecutable); return parameterContext; diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java similarity index 97% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java index 6e473e8d1965..609f83cd95d3 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/DefaultArgumentConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -49,8 +49,8 @@ import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; -import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link DefaultArgumentConverter}. @@ -132,7 +132,7 @@ void convertsStringsToPrimitiveWrapperTypes() { @ParameterizedTest(name = "[{index}] {0}") @ValueSource(classes = { char.class, boolean.class, short.class, byte.class, int.class, long.class, float.class, - double.class }) + double.class, void.class }) void throwsExceptionForNullToPrimitiveTypeConversion(Class type) { assertThatExceptionOfType(ArgumentConversionException.class) // .isThrownBy(() -> convert(null, type)) // @@ -261,8 +261,10 @@ void convertsStringToPath() { @Test void convertsStringToClass() { assertConverts("java.lang.Integer", Class.class, Integer.class); + assertConverts("java.lang.Void", Class.class, Void.class); assertConverts("java.lang.Thread$State", Class.class, State.class); assertConverts("byte", Class.class, byte.class); + assertConverts("void", Class.class, void.class); assertConverts("char[]", Class.class, char[].class); assertConverts("java.lang.Long[][]", Class.class, Long[][].class); assertConverts("[[[I", Class.class, int[][][].class); @@ -276,7 +278,7 @@ void convertsStringToClassWithCustomTypeFromDifferentClassLoader() throws Except var customType = testClassLoader.loadClass(customTypeName); assertThat(customType.getClassLoader()).isSameAs(testClassLoader); - var declaringExecutable = ReflectionUtils.findMethod(customType, "foo").get(); + var declaringExecutable = ReflectionSupport.findMethod(customType, "foo").get(); assertThat(declaringExecutable.getDeclaringClass().getClassLoader()).isSameAs(testClassLoader); var clazz = (Class) convert(customTypeName, Class.class, parameterContext(declaringExecutable)); @@ -373,7 +375,7 @@ private Object convert(Object input, Class targetClass, ParameterContext para } private static ParameterContext parameterContext() { - Method declaringExecutable = ReflectionUtils.findMethod(DefaultArgumentConverterTests.class, "foo").get(); + Method declaringExecutable = ReflectionSupport.findMethod(DefaultArgumentConverterTests.class, "foo").get(); return parameterContext(declaringExecutable); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java similarity index 83% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java index 3983886cd476..2cee9d79f7c9 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/JavaTimeArgumentConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -110,10 +110,35 @@ void throwsExceptionOnInvalidTargetType() { assertThat(exception).hasMessage("Cannot convert to java.lang.Integer: 2017"); } + /** + * @since 5.12 + */ + @Test + void throwsExceptionOnNullParameterWithoutNullable() { + var exception = assertThrows(ArgumentConversionException.class, + () -> convert(null, "dd.MM.yyyy", LocalDate.class)); + + assertThat(exception).hasMessage( + "Cannot convert null to java.time.LocalDate; consider setting 'nullable = true'"); + } + + /** + * @since 5.12 + */ + @Test + void convertsNullableParameter() { + assertThat(convert(null, "dd.MM.yyyy", true, LocalDate.class)).isNull(); + } + private Object convert(Object input, String pattern, Class targetClass) { + return convert(input, pattern, false, targetClass); + } + + private Object convert(Object input, String pattern, boolean nullable, Class targetClass) { var converter = new JavaTimeArgumentConverter(); var annotation = mock(JavaTimeConversionPattern.class); when(annotation.value()).thenReturn(pattern); + when(annotation.nullable()).thenReturn(nullable); return converter.convert(input, targetClass, annotation); } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java similarity index 96% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java index 0881c45231cd..599f3d2e9163 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/converter/TypedArgumentConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -30,7 +30,7 @@ import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.commons.support.ReflectionSupport; /** * Tests for {@link TypedArgumentConverter}. @@ -98,7 +98,7 @@ private ParameterContext parameterContext(Parameter parameter) { } private Parameter findParameterOfMethod(String methodName, Class... parameterTypes) { - Method method = ReflectionUtils.findMethod(getClass(), methodName, parameterTypes).get(); + Method method = ReflectionSupport.findMethod(getClass(), methodName, parameterTypes).get(); return method.getParameters()[0]; } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java similarity index 98% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java index af6e1eec06bd..0cbbb252204c 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java similarity index 96% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java index 3a56f283a2da..76dd591f7101 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ArgumentsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java similarity index 95% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java index 0ea40f9c70e9..4a1e9722e0a9 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvArgumentsProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; @@ -296,20 +297,21 @@ void throwsExceptionWhenSourceExceedsDefaultMaxCharsPerColumnConfig() { @Test void providesArgumentsForExceedsSourceWithCustomMaxCharsPerColumnConfig() { - var annotation = csvSource().lines("0".repeat(4097)).delimiter(';').maxCharsPerColumn(4097).build(); + var annotation = csvSource().lines("0".repeat(4097)).maxCharsPerColumn(4097).build(); var arguments = provideArguments(annotation); assertThat(arguments.toArray()).hasSize(1); } - @Test - void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber() { - var annotation = csvSource().lines("41").delimiter(';').maxCharsPerColumn(-1).build(); + @ParameterizedTest + @ValueSource(ints = { Integer.MIN_VALUE, -2, 0 }) + void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumberOrMinusOne(int maxCharsPerColumn) { + var annotation = csvSource().lines("41").maxCharsPerColumn(maxCharsPerColumn).build(); assertThatExceptionOfType(PreconditionViolationException.class)// .isThrownBy(() -> provideArguments(annotation).findAny())// - .withMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); + .withMessageStartingWith("maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); } @Test diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java similarity index 85% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java index afdce4749143..63f5a7aa3a76 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/CsvFileArgumentsProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,7 +19,6 @@ import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -30,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvFileArgumentsProvider.InputStreamProvider; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; @@ -158,7 +158,7 @@ public void close() { void readsFromSingleClasspathResource() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv")// + .resources("single-column.csv")// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); @@ -168,7 +168,7 @@ void readsFromSingleClasspathResource() { @Test void readsFromSingleFileWithAbsolutePath(@TempDir Path tempDir) throws Exception { - var csvFile = writeClasspathResourceToFile("/single-column.csv", tempDir.resolve("single-column.csv")); + var csvFile = writeClasspathResourceToFile("single-column.csv", tempDir.resolve("single-column.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// .files(csvFile.toAbsolutePath().toString())// @@ -181,10 +181,10 @@ void readsFromSingleFileWithAbsolutePath(@TempDir Path tempDir) throws Exception @Test void readsFromClasspathResourcesAndFiles(@TempDir Path tempDir) throws Exception { - var csvFile = writeClasspathResourceToFile("/single-column.csv", tempDir.resolve("single-column.csv")); + var csvFile = writeClasspathResourceToFile("single-column.csv", tempDir.resolve("single-column.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv")// + .resources("single-column.csv")// .files(csvFile.toAbsolutePath().toString())// .build(); @@ -195,7 +195,7 @@ void readsFromClasspathResourcesAndFiles(@TempDir Path tempDir) throws Exception @Test void readsFromSingleFileWithRelativePath() throws Exception { - var csvFile = writeClasspathResourceToFile("/single-column.csv", Path.of("single-column.csv")); + var csvFile = writeClasspathResourceToFile("single-column.csv", Path.of("single-column.csv")); try { var annotation = csvFileSource()// .encoding("ISO-8859-1")// @@ -215,7 +215,7 @@ void readsFromSingleFileWithRelativePath() throws Exception { void readsFromSingleClasspathResourceWithCustomEmptyValue() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv")// + .resources("single-column.csv")// .emptyValue("EMPTY")// .build(); @@ -228,7 +228,7 @@ void readsFromSingleClasspathResourceWithCustomEmptyValue() { void readsFromMultipleClasspathResources() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv", "/single-column.csv")// + .resources("single-column.csv", "single-column.csv")// .build(); var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); @@ -240,7 +240,7 @@ void readsFromMultipleClasspathResources() { void readsFromSingleClasspathResourceWithHeaders() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv")// + .resources("single-column.csv")// .numLinesToSkip(1)// .build(); @@ -253,7 +253,7 @@ void readsFromSingleClasspathResourceWithHeaders() { void readsFromSingleClasspathResourceWithMoreHeadersThanLines() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv")// + .resources("single-column.csv")// .numLinesToSkip(10)// .build(); @@ -266,7 +266,7 @@ void readsFromSingleClasspathResourceWithMoreHeadersThanLines() { void readsFromMultipleClasspathResourcesWithHeaders() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv", "/single-column.csv")// + .resources("single-column.csv", "single-column.csv")// .numLinesToSkip(1)// .build(); @@ -280,7 +280,7 @@ void readsFromMultipleClasspathResourcesWithHeaders() { void supportsCsvHeadersInDisplayNames() { var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/single-column.csv")// + .resources("single-column.csv")// .useHeadersInDisplayName(true)// .build(); @@ -370,7 +370,7 @@ void throwsExceptionForInvalidCharset() { @Test void throwsExceptionForInvalidCsvFormat() { var annotation = csvFileSource()// - .resources("/broken.csv")// + .resources("broken.csv")// .build(); var exception = assertThrows(CsvParsingException.class, @@ -397,10 +397,10 @@ void emptyValueIsAnEmptyWithCustomNullValueString() { @Test void readsLineFromDefaultMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws Exception { - var csvFile = writeClasspathResourceToFile("/default-max-chars.csv", tempDir.resolve("default-max-chars.csv")); + var csvFile = writeClasspathResourceToFile("default-max-chars.csv", tempDir.resolve("default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/default-max-chars.csv")// + .resources("default-max-chars.csv")// .files(csvFile.toAbsolutePath().toString())// .build(); @@ -410,12 +410,12 @@ void readsLineFromDefaultMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) th } @Test - void readsLineFromExceedsMaxCharsFileWithCustomConfig(@TempDir Path tempDir) throws java.io.IOException { - var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", + void readsLineFromExceedsMaxCharsFileWithCustomExplicitConfig(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("exceeds-default-max-chars.csv", tempDir.resolve("exceeds-default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/exceeds-default-max-chars.csv")// + .resources("exceeds-default-max-chars.csv")// .maxCharsPerColumn(4097)// .files(csvFile.toAbsolutePath().toString())// .build(); @@ -426,29 +426,54 @@ void readsLineFromExceedsMaxCharsFileWithCustomConfig(@TempDir Path tempDir) thr } @Test - void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumber(@TempDir Path tempDir) throws java.io.IOException { - var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", + void readsLineFromExceedsMaxCharsFileWithCustomUnlimitedConfig(@TempDir Path tempDir) throws Exception { + var csvFile = tempDir.resolve("test.csv"); + try (var out = Files.newBufferedWriter(csvFile)) { + var chunks = 10; + var chunk = "a".repeat(8192); + for (long i = 0; i < chunks; i++) { + out.write(chunk); + } + } + + var annotation = csvFileSource()// + .encoding("ISO-8859-1")// + .maxCharsPerColumn(-1)// + .files(csvFile.toAbsolutePath().toString())// + .build(); + + var arguments = provideArguments(new CsvFileArgumentsProvider(), annotation); + + assertThat(arguments).hasSize(1); + } + + @ParameterizedTest + @ValueSource(ints = { Integer.MIN_VALUE, -2, 0 }) + void throwsExceptionWhenMaxCharsPerColumnIsNotPositiveNumberOrMinusOne(int maxCharsPerColumn, @TempDir Path tempDir) + throws Exception { + var csvFile = writeClasspathResourceToFile("exceeds-default-max-chars.csv", tempDir.resolve("exceeds-default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/exceeds-default-max-chars.csv")// - .maxCharsPerColumn(-1).files(csvFile.toAbsolutePath().toString())// + .resources("exceeds-default-max-chars.csv")// + .maxCharsPerColumn(maxCharsPerColumn)// + .files(csvFile.toAbsolutePath().toString())// .build(); var exception = assertThrows(PreconditionViolationException.class, // () -> provideArguments(new CsvFileArgumentsProvider(), annotation).findAny()); assertThat(exception)// - .hasMessageStartingWith("maxCharsPerColumn must be a positive number: -1"); + .hasMessageStartingWith("maxCharsPerColumn must be a positive number or -1: " + maxCharsPerColumn); } @Test - void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws java.io.IOException { - var csvFile = writeClasspathResourceToFile("/exceeds-default-max-chars.csv", + void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("exceeds-default-max-chars.csv", tempDir.resolve("exceeds-default-max-chars.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/exceeds-default-max-chars.csv")// + .resources("exceeds-default-max-chars.csv")// .files(csvFile.toAbsolutePath().toString())// .build(); @@ -461,12 +486,12 @@ void throwsExceptionForExceedsMaxCharsFileWithDefaultConfig(@TempDir Path tempDi } @Test - void ignoresLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { - var csvFile = writeClasspathResourceToFile("/leading-trailing-spaces.csv", + void ignoresLeadingAndTrailingSpaces(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("leading-trailing-spaces.csv", tempDir.resolve("leading-trailing-spaces.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/leading-trailing-spaces.csv")// + .resources("leading-trailing-spaces.csv")// .files(csvFile.toAbsolutePath().toString())// .ignoreLeadingAndTrailingWhitespace(true)// .build(); @@ -477,12 +502,12 @@ void ignoresLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { } @Test - void trimsLeadingAndTrailingSpaces(@TempDir Path tempDir) throws IOException { - var csvFile = writeClasspathResourceToFile("/leading-trailing-spaces.csv", + void trimsLeadingAndTrailingSpaces(@TempDir Path tempDir) throws Exception { + var csvFile = writeClasspathResourceToFile("leading-trailing-spaces.csv", tempDir.resolve("leading-trailing-spaces.csv")); var annotation = csvFileSource()// .encoding("ISO-8859-1")// - .resources("/leading-trailing-spaces.csv")// + .resources("leading-trailing-spaces.csv")// .files(csvFile.toAbsolutePath().toString())// .delimiter(',')// .ignoreLeadingAndTrailingWhitespace(false)// @@ -527,7 +552,7 @@ private static T[] array(T... elements) { return elements; } - private static Path writeClasspathResourceToFile(String name, Path target) throws IOException { + private static Path writeClasspathResourceToFile(String name, Path target) throws Exception { try (var in = CsvFileArgumentsProviderTests.class.getResourceAsStream(name)) { Files.copy(in, target); } diff --git a/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java new file mode 100644 index 000000000000..8d2d5cfbd170 --- /dev/null +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumArgumentsProviderTests.java @@ -0,0 +1,224 @@ +/* + * 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.provider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.BAR; +import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.BAZ; +import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.FOO; +import static org.junit.jupiter.params.provider.EnumArgumentsProviderTests.EnumWithFourConstants.QUX; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.platform.commons.PreconditionViolationException; + +/** + * @since 5.0 + */ +class EnumArgumentsProviderTests { + + private ExtensionContext extensionContext = mock(); + + @Test + void providesAllEnumConstants() { + var arguments = provideArguments(EnumWithFourConstants.class); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }, new Object[] { BAZ }, + new Object[] { QUX }); + } + + @Test + void provideSingleEnumConstant() { + var arguments = provideArguments(EnumWithFourConstants.class, "FOO"); + + assertThat(arguments).containsExactly(new Object[] { FOO }); + } + + @Test + void provideAllEnumConstantsWithNamingAll() { + var arguments = provideArguments(EnumWithFourConstants.class, "FOO", "BAR"); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }); + } + + @Test + void duplicateConstantNameIsDetected() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithFourConstants.class, "FOO", "BAR", "FOO").findAny()); + assertThat(exception).hasMessageContaining("Duplicate enum constant name(s) found"); + } + + @Test + void invalidConstantNameIsDetected() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithFourConstants.class, "FO0", "B4R").findAny()); + assertThat(exception).hasMessageContaining("Invalid enum constant name(s) in"); + } + + @Test + void invalidPatternIsDetected() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithFourConstants.class, Mode.MATCH_ALL, "(", ")").findAny()); + assertThat(exception).hasMessageContaining("Pattern compilation failed"); + } + + @Test + void providesEnumConstantsBasedOnTestMethod() throws Exception { + when(extensionContext.getRequiredTestMethod()).thenReturn( + TestCase.class.getDeclaredMethod("methodWithCorrectParameter", EnumWithFourConstants.class)); + + var arguments = provideArguments(NullEnum.class); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }, new Object[] { BAZ }, + new Object[] { QUX }); + } + + @Test + void incorrectParameterTypeIsDetected() throws Exception { + when(extensionContext.getRequiredTestMethod()).thenReturn( + TestCase.class.getDeclaredMethod("methodWithIncorrectParameter", Object.class)); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(NullEnum.class).findAny()); + assertThat(exception).hasMessageStartingWith("First parameter must reference an Enum type"); + } + + @Test + void methodsWithoutParametersAreDetected() throws Exception { + when(extensionContext.getRequiredTestMethod()).thenReturn( + TestCase.class.getDeclaredMethod("methodWithoutParameters")); + + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(NullEnum.class).findAny()); + assertThat(exception).hasMessageStartingWith("Test method must declare at least one parameter"); + } + + @Test + void providesEnumConstantsStartingFromBar() { + var arguments = provideArguments(EnumWithFourConstants.class, "BAR", "", Mode.INCLUDE); + + assertThat(arguments).containsExactly(new Object[] { BAR }, new Object[] { BAZ }, new Object[] { QUX }); + } + + @Test + void providesEnumConstantsEndingAtBaz() { + var arguments = provideArguments(EnumWithFourConstants.class, "", "BAZ", Mode.INCLUDE); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAR }, new Object[] { BAZ }); + } + + @Test + void providesEnumConstantsFromBarToBaz() { + var arguments = provideArguments(EnumWithFourConstants.class, "BAR", "BAZ", Mode.INCLUDE); + + assertThat(arguments).containsExactly(new Object[] { BAR }, new Object[] { BAZ }); + } + + @Test + void providesEnumConstantsFromFooToBazWhileExcludingBar() { + var arguments = provideArguments(EnumWithFourConstants.class, "FOO", "BAZ", Mode.EXCLUDE, "BAR"); + + assertThat(arguments).containsExactly(new Object[] { FOO }, new Object[] { BAZ }); + } + + @Test + void providesNoEnumConstant() { + var arguments = provideArguments(EnumWithNoConstant.class); + + assertThat(arguments).isEmpty(); + } + + @Test + void invalidConstantNameIsDetectedInRange() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithFourConstants.class, "FOO", "BAZ", Mode.EXCLUDE, "QUX").findAny()); + assertThat(exception).hasMessageContaining("Invalid enum constant name(s) in"); + } + + @Test + void invalidStartingRangeIsDetected() { + var exception = assertThrows(IllegalArgumentException.class, + () -> provideArguments(EnumWithFourConstants.class, "B4R", "", Mode.INCLUDE).findAny()); + assertThat(exception).hasMessageContaining("No enum constant"); + } + + @Test + void invalidEndingRangeIsDetected() { + var exception = assertThrows(IllegalArgumentException.class, + () -> provideArguments(EnumWithFourConstants.class, "", "B4R", Mode.INCLUDE).findAny()); + assertThat(exception).hasMessageContaining("No enum constant"); + } + + @Test + void invalidRangeOrderIsDetected() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithFourConstants.class, "BAR", "FOO", Mode.INCLUDE).findAny()); + assertThat(exception).hasMessageContaining("Invalid enum range"); + } + + @Test + void invalidRangeIsDetectedWhenEnumWithNoConstantIsProvided() { + var exception = assertThrows(PreconditionViolationException.class, + () -> provideArguments(EnumWithNoConstant.class, "BAR", "FOO", Mode.INCLUDE).findAny()); + assertThat(exception).hasMessageContaining("No enum constant"); + } + + static class TestCase { + void methodWithCorrectParameter(EnumWithFourConstants parameter) { + } + + void methodWithIncorrectParameter(Object parameter) { + } + + void methodWithoutParameters() { + } + } + + enum EnumWithFourConstants { + FOO, BAR, BAZ, QUX + } + + enum EnumWithNoConstant { + } + + private > Stream provideArguments(Class enumClass, String... names) { + return provideArguments(enumClass, Mode.INCLUDE, names); + } + + private > Stream provideArguments(Class enumClass, Mode mode, String... names) { + return provideArguments(enumClass, "", "", mode, names); + } + + private > Stream provideArguments(Class enumClass, String from, String to, Mode mode, + String... names) { + var annotation = mock(EnumSource.class); + when(annotation.value()).thenAnswer(__ -> enumClass); + when(annotation.from()).thenReturn(from); + when(annotation.to()).thenReturn(to); + when(annotation.mode()).thenReturn(mode); + when(annotation.names()).thenReturn(names); + when(annotation.toString()).thenReturn( + String.format("@EnumSource(value=%s.class, from=%s, to=%s, mode=%s, names=%s)", enumClass.getSimpleName(), + from, to, mode, Arrays.toString(names))); + + var provider = new EnumArgumentsProvider(); + provider.accept(annotation); + return provider.provideArguments(extensionContext).map(Arguments::get); + } + +} diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java similarity index 98% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java index 819dd6bd56df..1e37beb124c1 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/EnumSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java similarity index 98% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java index 3ab74b340d84..a8a395cea0ac 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/FieldArgumentsProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryWithDefaultExtensions; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.junit.platform.commons.support.ReflectionSupport.findMethod; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -36,8 +36,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.ReflectionSupport; import org.junit.platform.commons.test.TestClassLoader; -import org.junit.platform.commons.util.ReflectionUtils; /** * Unit tests for {@link FieldArgumentsProvider}. @@ -466,7 +466,7 @@ private static Stream provideArguments(Class testClass, boolean all // Ensure we have a non-null test method, even if it's not a real test method. // If this throws an exception, make sure that the supplied test class defines a "void test()" method. - Method testMethod = ReflectionUtils.findMethod(testClass, "test").get(); + Method testMethod = ReflectionSupport.findMethod(testClass, "test").get(); return provideArguments(testClass, testMethod, allowNonStaticMethod, fieldNames); } @@ -487,7 +487,7 @@ private static Stream provideArguments(Class testClass, Method test doCallRealMethod().when(extensionContext).getRequiredTestMethod(); doCallRealMethod().when(extensionContext).getRequiredTestClass(); - var testInstance = allowNonStaticMethod ? ReflectionUtils.newInstance(testClass) : null; + var testInstance = allowNonStaticMethod ? ReflectionSupport.newInstance(testClass) : null; when(extensionContext.getTestInstance()).thenReturn(Optional.ofNullable(testInstance)); var lifeCycle = allowNonStaticMethod ? Lifecycle.PER_CLASS : Lifecycle.PER_METHOD; diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java similarity index 99% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java index 54287492112e..50e8bcc83ee2 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MethodArgumentsProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java similarity index 99% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java index c3b3c10648bb..22434b0f243f 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/MockCsvAnnotationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java similarity index 99% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java index dea71ff0f727..bbfaf94b69f0 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/provider/ValueArgumentsProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java b/jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java similarity index 99% rename from junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java rename to jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java index b60da429e03e..b304ec8f4baa 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java +++ b/jupiter-tests/src/test/java/org/junit/jupiter/params/support/AnnotationConsumerInitializerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt similarity index 88% rename from junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt index b85229eb0ded..a10d58420c8d 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertTimeoutAssertionsTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -155,6 +155,43 @@ internal class KotlinAssertTimeoutAssertionsTests { assertMessageStartsWith(error, "Tempus Fugit ==> execution exceeded timeout of 10 ms by") } + @Test + fun `assertTimeout with value initialization in lambda`() { + val value: Int + + assertTimeout(ofMillis(500)) { value = 10 } + + assertEquals(10, value) + } + + @Test + fun `assertTimeout with message and value initialization in lambda`() { + val value: Int + + assertTimeout(ofMillis(500), "message") { value = 10 } + + assertEquals(10, value) + } + + @Test + fun `assertTimeout with message supplier and value initialization in lambda`() { + val value: Int + + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + val valueInMessageSupplier: Int + + assertTimeout( + timeout = ofMillis(500), + message = { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + "message" + }, + executable = { value = 10 } + ) + + assertEquals(10, value) + } + // -- executable - preemptively --- @Test @@ -287,6 +324,21 @@ internal class KotlinAssertTimeoutAssertionsTests { assertMessageEquals(error, "Tempus Fugit ==> execution timed out after 10 ms") } + @Test + fun `assertTimeoutPreemptively with message supplier and value initialization in lambda`() { + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + val valueInMessageSupplier: Int + + assertTimeoutPreemptively( + timeout = ofMillis(500), + message = { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + "message" + }, + executable = {} + ) + } + /** * Take a nap for 100 milliseconds. */ diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt similarity index 80% rename from junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt index 79593e7b7a5a..41feab4cdcd1 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinAssertionsTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -247,6 +247,91 @@ class KotlinAssertionsTests { assertMessageStartsWith(result, "Should be a String") } + @Test + fun `assertInstanceOf with compiler smart cast`() { + val maybeString: Any = "string" + + assertInstanceOf(maybeString) + assertFalse(maybeString.isEmpty()) // A smart cast to a String object. + } + + @Test + fun `assertInstanceOf with compiler nullable smart cast`() { + val maybeString: Any? = "string" + + assertInstanceOf(maybeString) + assertFalse(maybeString.isEmpty()) // A smart cast to a non-nullable String object. + } + + @Test + fun `assertInstanceOf with a null value`() { + val error = + assertThrows { + assertInstanceOf(null) + } + + assertMessageStartsWith(error, "Unexpected null value") + } + + @Test + fun `assertInstanceOf with message and compiler smart cast`() { + val maybeString: Any = "string" + + assertInstanceOf(maybeString, "maybeString is not an instance of String") + assertFalse(maybeString.isEmpty()) // A smart cast to a String object. + } + + @Test + fun `assertInstanceOf with message supplier and compiler smart cast`() { + val maybeString: Any = "string" + + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + val valueInMessageSupplier: Int + + assertInstanceOf(maybeString) { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "maybeString is not an instance of String" + } + + assertFalse(maybeString.isEmpty()) // A smart cast to a String object. + } + + @Test + fun `assertNull with compiler smart cast`() { + val nullableString: String? = null + + assertNull(nullableString) + // Even safe call is not allowed because compiler knows that nullableString is always null. + // nullableString?.isEmpty() + } + + @Test + fun `assertNull with message and compiler smart cast`() { + val nullableString: String? = null + + assertNull(nullableString, "nullableString is not null") + // Even safe call is not allowed because compiler knows that nullableString is always null. + // nullableString?.isEmpty() + } + + @Test + fun `assertNull with message supplier and compiler smart cast`() { + val nullableString: String? = null + + @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE") + val valueInMessageSupplier: Int + + assertNull(nullableString) { + valueInMessageSupplier = 20 // Val can be assigned in the message supplier lambda. + + "nullableString is not null" + } + + // Even safe call is not allowed because compiler knows that nullableString is always null. + // nullableString?.isEmpty() + } + companion object { fun assertExpectedExceptionTypes( multipleFailuresError: MultipleFailuresError, diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt similarity index 98% rename from junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt index 2b1311232187..d0e026669a22 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/api/KotlinFailAssertionsTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt similarity index 93% rename from junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt index 2a3737f56f0b..662b608b0f69 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/ArbitraryNamingKotlinTestCase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt similarity index 96% rename from junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt index ef87340692f6..baa72c6d7455 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerClassKotlinTestCase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt similarity index 96% rename from junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt index 463a39ad9473..494fcdacb03a 100644 --- a/junit-jupiter-engine/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/engine/kotlin/InstancePerMethodKotlinTestCase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestNameFormatterIntegrationTests.kt similarity index 95% rename from junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestNameFormatterIntegrationTests.kt index 3634f3bb555d..5ce7d3fabdae 100644 --- a/junit-jupiter-params/src/test/kotlin/ParameterizedTestNameFormatterIntegrationTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/ParameterizedTestNameFormatterIntegrationTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -7,9 +7,10 @@ * * https://www.eclipse.org/legal/epl-v20.html */ +package org.junit.jupiter.params + import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.TestInfo -import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource class ParameterizedTestNameFormatterIntegrationTests { diff --git a/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt similarity index 97% rename from junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt index c3b97696cc5c..479a625775cc 100644 --- a/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/ArgumentsAccessorKotlinTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt similarity index 95% rename from junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt rename to jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt index d12ac782b0f6..12d9b336860f 100644 --- a/junit-jupiter-params/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt +++ b/jupiter-tests/src/test/kotlin/org/junit/jupiter/params/aggregator/DisplayNameTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/jupiter-tests/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension similarity index 50% rename from junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension rename to jupiter-tests/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension index 758c3e555809..27a9f87d158c 100644 --- a/junit-jupiter-engine/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension +++ b/jupiter-tests/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -1 +1,2 @@ org.junit.jupiter.engine.extension.ServiceLoaderExtension +org.junit.jupiter.engine.extension.ConfigLoaderExtension diff --git a/jupiter-tests/src/test/resources/junit-platform.properties b/jupiter-tests/src/test/resources/junit-platform.properties new file mode 100644 index 000000000000..6efc0d5e85ce --- /dev/null +++ b/jupiter-tests/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true diff --git a/junit-jupiter-engine/src/test/resources/jupiter-testjar.jar b/jupiter-tests/src/test/resources/jupiter-testjar.jar similarity index 100% rename from junit-jupiter-engine/src/test/resources/jupiter-testjar.jar rename to jupiter-tests/src/test/resources/jupiter-testjar.jar diff --git a/junit-jupiter-engine/src/test/resources/log4j2-test.xml b/jupiter-tests/src/test/resources/log4j2-test.xml similarity index 100% rename from junit-jupiter-engine/src/test/resources/log4j2-test.xml rename to jupiter-tests/src/test/resources/log4j2-test.xml diff --git a/junit-jupiter-params/src/test/resources/broken.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/broken.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/broken.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/broken.csv diff --git a/junit-jupiter-params/src/test/resources/default-max-chars.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/default-max-chars.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/default-max-chars.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/default-max-chars.csv diff --git a/junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/exceeds-default-max-chars.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/exceeds-default-max-chars.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/exceeds-default-max-chars.csv diff --git a/junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/leading-trailing-spaces.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/leading-trailing-spaces.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/leading-trailing-spaces.csv diff --git a/junit-jupiter-params/src/test/resources/single-column.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/single-column.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/single-column.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/provider/single-column.csv diff --git a/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/two-column-with-headers.csv diff --git a/junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv b/jupiter-tests/src/test/resources/org/junit/jupiter/params/two-column.csv similarity index 100% rename from junit-jupiter-params/src/test/resources/org/junit/jupiter/params/two-column.csv rename to jupiter-tests/src/test/resources/org/junit/jupiter/params/two-column.csv diff --git a/platform-tests/platform-tests.gradle.kts b/platform-tests/platform-tests.gradle.kts index 183203351e94..dd8508f495c4 100644 --- a/platform-tests/platform-tests.gradle.kts +++ b/platform-tests/platform-tests.gradle.kts @@ -1,4 +1,5 @@ -import org.gradle.api.tasks.PathSensitivity.NONE + +import junitbuild.extensions.capitalized import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.internal.os.OperatingSystem @@ -9,6 +10,24 @@ plugins { id("junitbuild.jmh-conventions") } +val processStarter by sourceSets.creating { + java { + srcDir("src/processStarter/java") + } +} + +java { + registerFeature(processStarter.name) { + usingSourceSet(processStarter) + } +} + +val woodstox = configurations.dependencyScope("woodstox") +val woodstoxRuntimeClasspath = configurations.resolvable("woodstoxRuntimeClasspath") { + extendsFrom(configurations.testRuntimeClasspath.get()) + extendsFrom(woodstox.get()) +} + dependencies { // --- Things we are testing -------------------------------------------------- testImplementation(projects.junitPlatformCommons) @@ -28,24 +47,47 @@ dependencies { testImplementation(projects.junitJupiterEngine) testImplementation(testFixtures(projects.junitJupiterEngine)) testImplementation(libs.apiguardian) + testImplementation(libs.classgraph) testImplementation(libs.jfrunit) { exclude(group = "org.junit.vintage") } testImplementation(libs.joox) - testImplementation(libs.openTestReporting.tooling) + testImplementation(libs.openTestReporting.tooling.core) testImplementation(libs.picocli) testImplementation(libs.bundles.xmlunit) testImplementation(testFixtures(projects.junitJupiterApi)) + testImplementation(testFixtures(projects.junitPlatformReporting)) + testImplementation(projects.platformTests) { + capabilities { + requireFeature("process-starter") + } + } // --- Test run-time dependencies --------------------------------------------- - testRuntimeOnly(projects.junitVintageEngine) + val mavenizedProjects: List by rootProject + mavenizedProjects.filter { it.path != projects.junitPlatformConsoleStandalone.path }.forEach { + // Add all projects to the classpath for tests using classpath scanning + testRuntimeOnly(it) + } testRuntimeOnly(libs.groovy4) { because("`ReflectionUtilsTests.findNestedClassesWithInvalidNestedClassFile` needs it") } + woodstox(libs.woodstox) - // --- https://openjdk.java.net/projects/code-tools/jmh/ ----------------------- + // --- https://openjdk.java.net/projects/code-tools/jmh/ ---------------------- jmh(projects.junitJupiterApi) jmh(libs.junit4) + + // --- ProcessStarter dependencies -------------------------------------------- + processStarter.implementationConfigurationName(libs.groovy4) { + because("it provides convenience methods to handle process output") + } + processStarter.implementationConfigurationName(libs.commons.io) { + because("it uses TeeOutputStream") + } + processStarter.implementationConfigurationName(libs.opentest4j) { + because("it throws TestAbortedException") + } } jmh { @@ -72,23 +114,38 @@ tasks { test { // Additional inputs for remote execution with Test Distribution inputs.dir("src/test/resources").withPathSensitivity(RELATIVE) - inputs.file(buildFile).withPathSensitivity(NONE) // for UniqueIdTrackingListenerIntegrationTests } test_4_12 { useJUnitPlatform { includeTags("junit4") } } + val testWoodstox by registering(Test::class) { + val test by testing.suites.existing(JvmTestSuite::class) + testClassesDirs = files(test.map { it.sources.output.classesDirs }) + classpath = files(sourceSets.main.map { it.output }) + files(test.map { it.sources.output }) + woodstoxRuntimeClasspath.get() + group = JavaBasePlugin.VERIFICATION_GROUP + setIncludes(listOf("**/org/junit/platform/reporting/**")) + } + check { + dependsOn(testWoodstox) + } + named(processStarter.compileJavaTaskName).configure { + options.release = javaLibrary.testJavaVersion.majorVersion.toInt() + } + named("checkstyle${processStarter.name.capitalized()}").configure { + config = resources.text.fromFile(checkstyle.configDirectory.file("checkstyleMain.xml")) + } } eclipse { classpath { - plusConfigurations.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowedClasspath"]) + plusConfigurations.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) } } idea { module { - scopes["PROVIDED"]!!["plus"]!!.add(projects.junitPlatformConsole.dependencyProject.configurations["shadowedClasspath"]) + scopes["PROVIDED"]!!["plus"]!!.add(dependencyProject(projects.junitPlatformConsole).configurations["shadowedClasspath"]) } } diff --git a/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java b/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java index 6b0940f6de80..79824e02ca90 100644 --- a/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java +++ b/platform-tests/src/jmh/java/org/junit/jupiter/jmh/AssertionBenchmarks.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/OutputFiles.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/OutputFiles.java new file mode 100644 index 000000000000..61208e404262 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/OutputFiles.java @@ -0,0 +1,16 @@ +/* + * 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.tests.process; + +import java.nio.file.Path; + +public record OutputFiles(Path stdOut, Path stdErr) { +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java new file mode 100644 index 000000000000..a05302abef85 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessResult.java @@ -0,0 +1,24 @@ +/* + * 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.tests.process; + +import java.util.List; + +public record ProcessResult(int exitCode, String stdOut, String stdErr) { + + public List stdOutLines() { + return stdOut.lines().toList(); + } + + public List stdErrLines() { + return stdErr.lines().toList(); + } +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java new file mode 100644 index 000000000000..e438c017a0c0 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/ProcessStarter.java @@ -0,0 +1,120 @@ +/* + * 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.tests.process; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import org.apache.commons.io.output.TeeOutputStream; +import org.codehaus.groovy.runtime.ProcessGroovyMethods; + +public class ProcessStarter { + + public static final Charset OUTPUT_ENCODING = Charset.forName(System.getProperty("native.encoding")); + + private Path executable; + private Path workingDir; + private final List arguments = new ArrayList<>(); + private final Map environment = new LinkedHashMap<>(); + private Optional outputFiles = Optional.empty(); + + public ProcessStarter executable(Path executable) { + this.executable = executable; + return this; + } + + public ProcessStarter workingDir(Path workingDir) { + this.workingDir = workingDir; + return this; + } + + public ProcessStarter addArguments(String... arguments) { + this.arguments.addAll(List.of(arguments)); + return this; + } + + public ProcessStarter putEnvironment(String key, Path value) { + return putEnvironment(key, value.toAbsolutePath().toString()); + } + + public ProcessStarter putEnvironment(String key, String value) { + environment.put(key, value); + return this; + } + + public ProcessStarter putEnvironment(Map values) { + environment.putAll(values); + return this; + } + + public ProcessStarter redirectOutput(OutputFiles outputFiles) { + this.outputFiles = Optional.of(outputFiles); + return this; + } + + public ProcessResult startAndWait() throws InterruptedException { + return start().waitFor(); + } + + public WatchedProcess start() { + var command = Stream.concat(Stream.of(executable.toString()), arguments.stream()).toList(); + try { + var builder = new ProcessBuilder().command(command); + if (workingDir != null) { + builder.directory(workingDir.toFile()); + } + builder.environment().putAll(environment); + var process = builder.start(); + var out = forwardAndCaptureOutput(process, System.out, outputFiles.map(OutputFiles::stdOut), + ProcessGroovyMethods::consumeProcessOutputStream); + var err = forwardAndCaptureOutput(process, System.err, outputFiles.map(OutputFiles::stdErr), + ProcessGroovyMethods::consumeProcessErrorStream); + return new WatchedProcess(process, out, err); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to start process: " + command, e); + } + } + + private static WatchedOutput forwardAndCaptureOutput(Process process, PrintStream delegate, + Optional outputFile, BiFunction captureAction) { + var capturingStream = new ByteArrayOutputStream(); + Optional fileStream = outputFile.map(path -> { + try { + return Files.newOutputStream(path); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to open output file: " + path, e); + } + }); + var attachedStream = tee(delegate, fileStream.map(it -> tee(capturingStream, it)).orElse(capturingStream)); + var thread = captureAction.apply(process, attachedStream); + return new WatchedOutput(thread, capturingStream, fileStream); + } + + private static OutputStream tee(OutputStream out, OutputStream branch) { + return new TeeOutputStream(out, branch); + } + +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java new file mode 100644 index 000000000000..a2c35e9e9a4d --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedOutput.java @@ -0,0 +1,24 @@ +/* + * 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.tests.process; + +import static org.junit.platform.tests.process.ProcessStarter.OUTPUT_ENCODING; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.Optional; + +record WatchedOutput(Thread thread, ByteArrayOutputStream stream, Optional fileStream) { + + String streamAsString() { + return stream.toString(OUTPUT_ENCODING); + } +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java new file mode 100644 index 000000000000..b3a864bce6ff --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/WatchedProcess.java @@ -0,0 +1,68 @@ +/* + * 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.tests.process; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Optional; + +public class WatchedProcess { + + private final Process process; + private final WatchedOutput out; + private final WatchedOutput err; + + WatchedProcess(Process process, WatchedOutput out, WatchedOutput err) { + this.process = process; + this.out = out; + this.err = err; + } + + ProcessResult waitFor() throws InterruptedException { + try { + int exitCode; + try { + try { + exitCode = process.waitFor(); + } + catch (InterruptedException e) { + process.destroyForcibly(); + throw e; + } + } + finally { + try { + out.thread().join(); + } + finally { + err.thread().join(); + } + } + return new ProcessResult(exitCode, out.streamAsString(), err.streamAsString()); + } + finally { + process.destroyForcibly(); + closeQuietly(out.fileStream()); + closeQuietly(err.fileStream()); + } + } + + private static void closeQuietly(Optional fileStream) { + if (fileStream.isEmpty()) { + return; + } + try { + fileStream.get().close(); + } + catch (IOException ignore) { + } + } +} diff --git a/platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java new file mode 100644 index 000000000000..35d962f9c545 --- /dev/null +++ b/platform-tests/src/processStarter/java/org/junit/platform/tests/process/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities for working with processes. + * + * @since 1.12 + */ + +package org.junit.platform.tests.process; diff --git a/platform-tests/src/test/java/DefaultPackageTestCase.java b/platform-tests/src/test/java/DefaultPackageTestCase.java index 5acabf2e975d..600942ad8147 100644 --- a/platform-tests/src/test/java/DefaultPackageTestCase.java +++ b/platform-tests/src/test/java/DefaultPackageTestCase.java @@ -1,6 +1,6 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java b/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java index 971e6ab9e035..70a0c42350e1 100644 --- a/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java +++ b/platform-tests/src/test/java/org/junit/jupiter/api/condition/OSTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java b/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java index 208b0a5cac27..ce5370f1ec28 100644 --- a/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java +++ b/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java b/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java index 5f0aaf1911be..6c278c9ae8c2 100644 --- a/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java +++ b/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java b/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java index 456fcf9a5117..097994002ddc 100644 --- a/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/JUnitPlatformTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/StackTracePruningTests.java b/platform-tests/src/test/java/org/junit/platform/StackTracePruningTests.java index 1babd73c2766..8c36d36b9929 100644 --- a/platform-tests/src/test/java/org/junit/platform/StackTracePruningTests.java +++ b/platform-tests/src/test/java/org/junit/platform/StackTracePruningTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -195,6 +195,7 @@ private static void assertStackTraceDoesNotContain(List stack // ------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class FailingTestTestCase { @Test @@ -216,6 +217,7 @@ void failingAssumption() { } + @SuppressWarnings("JUnitMalformedDeclaration") static class FailingBeforeEachTestCase { @BeforeEach diff --git a/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java b/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java index a5ad5f3ccbdb..02fac34e7604 100644 --- a/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java +++ b/platform-tests/src/test/java/org/junit/platform/TestEngineTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java b/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java index 894550f22604..297d3add4993 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/annotation/TestableAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java b/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java index 54716dc3fad5..4ad2dce22055 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/function/TryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java index 33d7d0caeaa1..b882977606a1 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/AnnotationSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.Tag; @@ -80,16 +81,24 @@ void findAnnotationOnElementDelegates() { AnnotationSupport.findAnnotation(element, Override.class)); } + @SuppressWarnings("deprecation") @Test - void findAnnotationOnClassPreconditions() { + void findAnnotationOnClassWithSearchModePreconditions() { assertPreconditionViolationException("annotationType", () -> AnnotationSupport.findAnnotation(Probe.class, null, SearchOption.INCLUDE_ENCLOSING_CLASSES)); assertPreconditionViolationException("SearchOption", - () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, null)); + () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (SearchOption) null)); } @Test - void findAnnotationOnClassDelegates() { + void findAnnotationOnClassWithEnclosingInstanceTypesPreconditions() { + assertPreconditionViolationException("enclosingInstanceTypes", + () -> AnnotationSupport.findAnnotation(Probe.class, Override.class, (List>) null)); + } + + @SuppressWarnings("deprecation") + @Test + void findAnnotationOnClassWithSearchModeDelegates() { Class clazz = Probe.class; assertEquals(AnnotationUtils.findAnnotation(clazz, Tag.class, false), AnnotationSupport.findAnnotation(clazz, Tag.class, SearchOption.DEFAULT)); @@ -111,6 +120,16 @@ void findAnnotationOnClassDelegates() { AnnotationSupport.findAnnotation(clazz, Override.class, SearchOption.INCLUDE_ENCLOSING_CLASSES)); } + @Test + void findAnnotationOnClassWithEnclosingInstanceTypes() { + assertThat(AnnotationSupport.findAnnotation(Probe.class, Tag.class, List.of())) // + .contains(Probe.class.getDeclaredAnnotation(Tag.class)); + assertThat(AnnotationSupport.findAnnotation(Probe.InnerClass.class, Tag.class, List.of())) // + .isEmpty(); + assertThat(AnnotationSupport.findAnnotation(Probe.InnerClass.class, Tag.class, List.of(Probe.class))) // + .contains(Probe.class.getDeclaredAnnotation(Tag.class)); + } + @Test void findPublicAnnotatedFieldsPreconditions() { assertPreconditionViolationException("Class", @@ -301,6 +320,7 @@ void aMethod() { void bMethod() { } + @SuppressWarnings("InnerClassMayBeStatic") class InnerClass { } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java index ebde2ce33e10..e2b972348253 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ClassSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java index 81fc84a10325..27e1092c4441 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ModifierSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -45,6 +45,7 @@ void isPublicDelegates(Class clazz) { assertEquals(ReflectionUtils.isPublic(clazz), ModifierSupport.isPublic(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isPublicDelegates(Method method) { assertEquals(ReflectionUtils.isPublic(method), ModifierSupport.isPublic(method)); @@ -61,6 +62,7 @@ void isPrivateDelegates(Class clazz) { assertEquals(ReflectionUtils.isPrivate(clazz), ModifierSupport.isPrivate(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isPrivate(method), ModifierSupport.isPrivate(method)); @@ -77,6 +79,7 @@ void isNotPrivateDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotPrivate(clazz), ModifierSupport.isNotPrivate(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isNotPrivateDelegates(Method method) { assertEquals(ReflectionUtils.isNotPrivate(method), ModifierSupport.isNotPrivate(method)); @@ -93,6 +96,7 @@ void isAbstractDelegates(Class clazz) { assertEquals(ReflectionUtils.isAbstract(clazz), ModifierSupport.isAbstract(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isAbstractDelegates(Method method) { assertEquals(ReflectionUtils.isAbstract(method), ModifierSupport.isAbstract(method)); @@ -109,6 +113,7 @@ void isStaticDelegates(Class clazz) { assertEquals(ReflectionUtils.isStatic(clazz), ModifierSupport.isStatic(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isStaticDelegates(Method method) { assertEquals(ReflectionUtils.isStatic(method), ModifierSupport.isStatic(method)); @@ -125,6 +130,7 @@ void isNotStaticDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotStatic(clazz), ModifierSupport.isNotStatic(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isNotStaticDelegates(Method method) { assertEquals(ReflectionUtils.isNotStatic(method), ModifierSupport.isNotStatic(method)); @@ -141,6 +147,7 @@ void isFinalDelegates(Class clazz) { assertEquals(ReflectionUtils.isFinal(clazz), ModifierSupport.isFinal(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isFinalDelegates(Method method) { assertEquals(ReflectionUtils.isFinal(method), ModifierSupport.isFinal(method)); @@ -157,6 +164,7 @@ void isNotFinalDelegates(Class clazz) { assertEquals(ReflectionUtils.isNotFinal(clazz), ModifierSupport.isNotFinal(clazz)); } + @SuppressWarnings("JUnitMalformedDeclaration") @Methods void isNotFinalDelegates(Method method) { assertEquals(ReflectionUtils.isNotFinal(method), ModifierSupport.isNotFinal(method)); diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java b/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java index 3a8060479a10..d8a396e7084c 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/PreconditionAssertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java index adb986154b79..0d1d9fc05454 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/ReflectionSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationException; import static org.junit.platform.commons.support.PreconditionAssertions.assertPreconditionViolationExceptionForString; +import static org.junit.platform.commons.util.ClassLoaderUtils.getDefaultClassLoader; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -119,6 +120,31 @@ List findAllClassesInClasspathRootDelegates() throws Throwable { return tests; } + /** + * @since 1.12 + */ + @Test + void tryToGetResourcesPreconditions() { + assertPreconditionViolationExceptionForString("Resource name", () -> ReflectionSupport.tryToGetResources(null)); + assertPreconditionViolationExceptionForString("Resource name", () -> ReflectionSupport.tryToGetResources("")); + assertPreconditionViolationException("Class loader", + () -> ReflectionSupport.tryToGetResources("default-package.resource", null)); + assertPreconditionViolationException("Class loader", + () -> ReflectionSupport.tryToGetResources("default-package.resource", null)); + } + + /** + * @since 1.12 + */ + @Test + void tryToGetResources() { + assertEquals(ReflectionUtils.tryToGetResources("default-package.resource").toOptional(), + ReflectionSupport.tryToGetResources("default-package.resource").toOptional()); + assertEquals( + ReflectionUtils.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional(), // + ReflectionSupport.tryToGetResources("default-package.resource", getDefaultClassLoader()).toOptional()); + } + @Test void findAllClassesInClasspathRootPreconditions() { var path = Path.of(".").toUri(); @@ -142,8 +168,9 @@ List findAllResourcesInClasspathRootDelegates() throws Throwable { for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), - () -> assertEquals(ReflectionUtils.findAllResourcesInClasspathRoot(root, allResources), - ReflectionSupport.findAllResourcesInClasspathRoot(root, allResources)))); + () -> assertThat(ReflectionUtils.findAllResourcesInClasspathRoot(root, allResources)) // + .containsExactlyElementsOf( + ReflectionSupport.findAllResourcesInClasspathRoot(root, allResources)))); } return tests; } @@ -172,8 +199,9 @@ List streamAllResourcesInClasspathRootDelegates() throws Throwable for (var path : paths) { var root = path.toUri(); tests.add(DynamicTest.dynamicTest(createDisplayName(root), - () -> assertEquals(ReflectionUtils.streamAllResourcesInClasspathRoot(root, allResources).toList(), - ReflectionSupport.streamAllResourcesInClasspathRoot(root, allResources).toList()))); + () -> assertThat(ReflectionUtils.streamAllResourcesInClasspathRoot(root, allResources)) // + .containsExactlyElementsOf( + ReflectionSupport.streamAllResourcesInClasspathRoot(root, allResources).toList()))); } return tests; } 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 9994bba3858c..4f4a07da3409 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.platform.commons.support.conversion; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.platform.commons.util.ReflectionUtils.findMethod; +import static org.junit.platform.commons.support.ReflectionSupport.findMethod; import java.lang.reflect.Constructor; import java.lang.reflect.Method; diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/CloseablePathTests.java similarity index 89% rename from platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java rename to platform-tests/src/test/java/org/junit/platform/commons/support/scanning/CloseablePathTests.java index 512eefeb41ec..27081f85e92d 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/CloseablePathTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/CloseablePathTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,12 +8,11 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; -import static org.junit.platform.commons.util.CloseablePath.JAR_URI_SCHEME; +import static org.junit.platform.commons.support.scanning.CloseablePath.JAR_URI_SCHEME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.only; @@ -32,7 +31,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.CloseablePath.FileSystemProvider; +import org.junit.platform.commons.support.scanning.CloseablePath.FileSystemProvider; +import org.junit.platform.commons.test.ConcurrencyTestingUtils; import org.junit.platform.engine.support.hierarchical.OpenTest4JAwareThrowableCollector; class CloseablePathTests { @@ -92,7 +92,8 @@ void createsAndClosesJarFileSystemOnceWhenCalledConcurrently() throws Exception when(fileSystemProvider.newFileSystem(any())) // .thenAnswer(invocation -> FileSystems.newFileSystem((URI) invocation.getArgument(0), Map.of())); - paths = executeConcurrently(numThreads, () -> CloseablePath.create(uri, fileSystemProvider)); + paths = ConcurrencyTestingUtils.executeConcurrently(numThreads, + () -> CloseablePath.create(uri, fileSystemProvider)); verify(fileSystemProvider, only()).newFileSystem(jarUri); // Close all but the first path diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java similarity index 90% rename from platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java rename to platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java index 7cd2f31456b4..c99ae9b6b51f 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClasspathScannerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/support/scanning/DefaultClasspathScannerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -8,7 +8,7 @@ * https://www.eclipse.org/legal/epl-v20.html */ -package org.junit.platform.commons.util; +package org.junit.platform.commons.support.scanning; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; @@ -50,14 +50,16 @@ import org.junit.platform.commons.function.Try; import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.commons.support.Resource; +import org.junit.platform.commons.util.ClassLoaderUtils; +import org.junit.platform.commons.util.ReflectionUtils; /** - * Unit tests for {@link ClasspathScanner}. + * Unit tests for {@link DefaultClasspathScanner}. * * @since 1.0 */ @TrackLogRecords -class ClasspathScannerTests { +class DefaultClasspathScannerTests { private static final ClassFilter allClasses = ClassFilter.of(type -> true); private static final Predicate allResources = type -> true; @@ -67,8 +69,8 @@ class ClasspathScannerTests { private final BiFunction>> trackingClassLoader = (name, classLoader) -> ReflectionUtils.tryToLoadClass(name, classLoader).ifSuccess(loadedClasses::add); - private final ClasspathScanner classpathScanner = new ClasspathScanner(ClassLoaderUtils::getDefaultClassLoader, - trackingClassLoader); + private final DefaultClasspathScanner classpathScanner = new DefaultClasspathScanner( + ClassLoaderUtils::getDefaultClassLoader, trackingClassLoader); @Test void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccursWithNullDetailedMessage( @@ -152,7 +154,7 @@ private void assertResourcesScannedWhenExceptionIsThrown(Predicate fil private void assertDebugMessageLogged(LogRecordListener listener, String regex) { // @formatter:off - assertThat(listener.stream(ClasspathScanner.class, Level.FINE) + assertThat(listener.stream(DefaultClasspathScanner.class, Level.FINE) .map(LogRecord::getMessage) .filter(m -> m.matches(regex)) ).hasSize(1); @@ -187,7 +189,7 @@ private void scanForClassesInClasspathRootWithinJarFile(String resourceName) thr var jarfile = getClass().getResource(resourceName); try (var classLoader = new URLClassLoader(new URL[] { jarfile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), allClasses); assertThat(classes).extracting(Class::getName) // @@ -211,7 +213,7 @@ private void scanForResourcesInClasspathRootWithinJarFile(String resourceName) t var jarfile = getClass().getResource(resourceName); try (var classLoader = new URLClassLoader(new URL[] { jarfile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInClasspathRoot(jarfile.toURI(), allResources); assertThat(resources).extracting(Resource::getName) // @@ -228,7 +230,7 @@ void scanForResourcesInShadowedClassPathRoot() throws Exception { var shadowedJarFile = getClass().getResource("/jartest-shadowed.jar"); try (var classLoader = new URLClassLoader(new URL[] { jarFile, shadowedJarFile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInClasspathRoot(shadowedJarFile.toURI(), allResources); assertThat(resources).extracting(Resource::getName).containsExactlyInAnyOrder( @@ -238,7 +240,7 @@ void scanForResourcesInShadowedClassPathRoot() throws Exception { "META-INF/MANIFEST.MF"); assertThat(resources).extracting(Resource::getUri) // - .map(ClasspathScannerTests::jarFileAndEntry) // + .map(DefaultClasspathScannerTests::jarFileAndEntry) // .containsExactlyInAnyOrder( // This resource only exists in the shadowed jar file "jartest-shadowed.jar!/org/junit/platform/jartest/included/unique.resource", @@ -256,13 +258,13 @@ void scanForResourcesInPackageWithDuplicateResources() throws Exception { var shadowedJarFile = getClass().getResource("/jartest-shadowed.jar"); try (var classLoader = new URLClassLoader(new URL[] { jarFile, shadowedJarFile }, null)) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var resources = classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources); assertThat(resources).extracting(Resource::getUri) // - .map(ClasspathScannerTests::jarFileAndEntry) // + .map(DefaultClasspathScannerTests::jarFileAndEntry) // .containsExactlyInAnyOrder( // This resource only exists in the shadowed jar file "jartest-shadowed.jar!/org/junit/platform/jartest/included/unique.resource", @@ -329,7 +331,8 @@ private void checkModules2500(ModuleFinder finder) { var parent = ClassLoader.getPlatformClassLoader(); var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer(); - var classpathScanner = new ClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> layer.findLoader(root), + ReflectionUtils::tryToLoadClass); { var classes = classpathScanner.scanForClassesInPackage("foo", allClasses); var classNames = classes.stream().map(Class::getName).collect(Collectors.toList()); @@ -348,7 +351,7 @@ void findAllClassesInPackageWithinJarFileConcurrently() throws Exception { var jarUri = URI.create("jar:" + jarFile); try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var results = executeConcurrently(10, () -> classpathScanner.scanForClassesInPackage("org.junit.platform.jartest.included", allClasses)); @@ -369,7 +372,7 @@ void findAllResourcesInPackageWithinJarFileConcurrently() throws Exception { var jarUri = URI.create("jar:" + jarFile); try (var classLoader = new URLClassLoader(new URL[] { jarFile })) { - var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); + var classpathScanner = new DefaultClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass); var results = executeConcurrently(10, () -> classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources)); @@ -410,9 +413,9 @@ void scanForResourcesInDefaultPackage() { @Test void scanForClassesInPackageWithFilter() { - var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); + var thisClassOnly = ClassFilter.of(clazz -> clazz == DefaultClasspathScannerTests.class); var classes = classpathScanner.scanForClassesInPackage("org.junit.platform.commons", thisClassOnly); - assertSame(ClasspathScannerTests.class, classes.get(0)); + assertSame(DefaultClasspathScannerTests.class, classes.get(0)); } @Test @@ -471,34 +474,34 @@ void scanForClassesInPackageForNullClassFilter() { @Test void scanForClassesInPackageWhenIOExceptionOccurs() { - var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); + var scanner = new DefaultClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); var classes = scanner.scanForClassesInPackage("org.junit.platform.commons", allClasses); assertThat(classes).isEmpty(); } @Test void scanForResourcesInPackageWhenIOExceptionOccurs() { - var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); + var scanner = new DefaultClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass); var classes = scanner.scanForResourcesInPackage("org.junit.platform.commons", allResources); assertThat(classes).isEmpty(); } @Test void scanForClassesInPackageOnlyLoadsClassesThatAreIncludedByTheClassNameFilter() { - Predicate classNameFilter = name -> ClasspathScannerTests.class.getName().equals(name); + Predicate classNameFilter = name -> DefaultClasspathScannerTests.class.getName().equals(name); var classFilter = ClassFilter.of(classNameFilter, type -> true); classpathScanner.scanForClassesInPackage("org.junit.platform.commons", classFilter); - assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); + assertThat(loadedClasses).containsExactly(DefaultClasspathScannerTests.class); } @Test void findAllClassesInClasspathRoot() throws Exception { - var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class); + var thisClassOnly = ClassFilter.of(clazz -> clazz == DefaultClasspathScannerTests.class); var root = getTestClasspathRoot(); var classes = classpathScanner.scanForClassesInClasspathRoot(root, thisClassOnly); - assertSame(ClasspathScannerTests.class, classes.get(0)); + assertSame(DefaultClasspathScannerTests.class, classes.get(0)); } @Test @@ -543,7 +546,7 @@ void findAllClassesInClasspathRootWithFilter() throws Exception { var classes = classpathScanner.scanForClassesInClasspathRoot(root, allClasses); assertThat(classes).hasSizeGreaterThanOrEqualTo(20); - assertTrue(classes.contains(ClasspathScannerTests.class)); + assertTrue(classes.contains(DefaultClasspathScannerTests.class)); } @Test @@ -566,16 +569,17 @@ void findAllClassesInClasspathRootForNullClassFilter() { @Test void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws Exception { - var classFilter = ClassFilter.of(name -> ClasspathScannerTests.class.getName().equals(name), type -> true); + var classFilter = ClassFilter.of(name -> DefaultClasspathScannerTests.class.getName().equals(name), + type -> true); var root = getTestClasspathRoot(); classpathScanner.scanForClassesInClasspathRoot(root, classFilter); - assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class); + assertThat(loadedClasses).containsExactly(DefaultClasspathScannerTests.class); } private static URI uriOf(String name) { - var resource = ClasspathScannerTests.class.getResource(name); + var resource = DefaultClasspathScannerTests.class.getResource(name); try { return requireNonNull(resource).toURI(); } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java index 7360a9505eee..e6865357192c 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/AnnotationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.platform.commons.util; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -273,8 +272,7 @@ void findRepeatableAnnotationsWithComposedTagBeforeContainer() { } private void assertTagsFound(Class clazz, String... tags) { - assertEquals(List.of(tags), - findRepeatableAnnotations(clazz, Tag.class).stream().map(Tag::value).collect(toList()), + assertEquals(List.of(tags), findRepeatableAnnotations(clazz, Tag.class).stream().map(Tag::value).toList(), () -> "Tags found for class " + clazz.getName()); } @@ -332,7 +330,7 @@ void findInheritedRepeatableAnnotationsWithMultipleComposedAnnotationsOnSupercla private void assertExtensionsFound(Class clazz, String... tags) { assertEquals(List.of(tags), - findRepeatableAnnotations(clazz, ExtendWith.class).stream().map(ExtendWith::value).collect(toList()), + findRepeatableAnnotations(clazz, ExtendWith.class).stream().map(ExtendWith::value).toList(), () -> "Extensions found for class " + clazz.getName()); } @@ -389,8 +387,8 @@ void findAnnotatedMethodsForAnnotationUsedInClassAndSuperclassHierarchyDown() th assertThat(methods.subList(1, 3)).containsOnly(method1, method3); } - /** - * @see https://github.com/junit-team/junit5/issues/3553 + /* + * see https://github.com/junit-team/junit5/issues/3553 */ @Test void findAnnotatedMethodsDoesNotAllowInstanceMethodToHideStaticMethod() throws Exception { @@ -523,8 +521,8 @@ private List findShadowingAnnotatedFields(Class an return values.stream().map(String::valueOf).toList(); } - /** - * @see https://github.com/junit-team/junit5/issues/3553 + /* + * see https://github.com/junit-team/junit5/issues/3553 */ @Test void findAnnotatedFieldsDoesNotAllowInstanceFieldToHideStaticField() throws Exception { @@ -610,7 +608,7 @@ void findPublicAnnotatedFieldsForDirectlyAnnotatedFieldsInClassAndInterface() { } private List asNames(List fields) { - return fields.stream().map(Field::getName).collect(toList()); + return fields.stream().map(Field::getName).toList(); } // ------------------------------------------------------------------------- diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java index a80cd18242ea..3d8a72df921a 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassLoaderUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java index 1d68086db38b..5204a27bf5d6 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassNamePatternFilterUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -169,4 +169,155 @@ void alwaysExcludedClassName(String pattern) { .isEmpty(); } + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AExecutionConditionClass, BExecutionConditionClass" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedConditions(String pattern) { + List executionConditions = List.of(new AExecutionConditionClass(), + new BExecutionConditionClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AExecutionConditionClass, *BExecutionConditionClass", + "*ExecutionConditionClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedConditions(String pattern) { + List executionConditions = List.of(new AExecutionConditionClass(), + new BExecutionConditionClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "ATestExecutionListenerClass, BTestExecutionListenerClass" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedListeners(String pattern) { + List executionConditions = List.of(new ATestExecutionListenerClass(), + new BTestExecutionListenerClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*ATestExecutionListenerClass, *BTestExecutionListenerClass", + "*TestExecutionListenerClass" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedListeners(String pattern) { + List executionConditions = List.of(new ATestExecutionListenerClass(), + new BTestExecutionListenerClass()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AVanillaEmpty, BVanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedClass(String pattern) { + var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedClass(String pattern) { + var executionConditions = List.of(new AVanillaEmpty(), new BVanillaEmpty()); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClasses(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.jupiter.*", + "org.junit.platform.*.NonExistentClass", + "*.NonExistentClass*", + "*NonExistentClass*", + "AVanillaEmpty, BVanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void neverIncludedClassName(String pattern) { + var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", + "org.junit.platform.commons.util.classes.BVanillaEmpty"); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // + .isEmpty(); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void alwaysIncludedClassName(String pattern) { + var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", + "org.junit.platform.commons.util.classes.BVanillaEmpty"); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // + .hasSize(2); + } + + //@formatter:off + @ValueSource(strings = { + "org.junit.platform.*", + "*.platform.*", + "*", + "*AVanillaEmpty, *BVanillaEmpty", + "*VanillaEmpty" + }) + //@formatter:on + @ParameterizedTest + void includeAndExcludeSame(String pattern) { + var executionConditions = List.of("org.junit.platform.commons.util.classes.AVanillaEmpty", + "org.junit.platform.commons.util.classes.BVanillaEmpty"); + assertThat(executionConditions).filteredOn(ClassNamePatternFilterUtils.includeMatchingClassNames(pattern)) // + .hasSize(2); + } + } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java index 8a683324b0e4..a73af19f3169 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ClassUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 ddc08e339f93..9e6f01daccbb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.platform.commons.util; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; @@ -260,7 +259,7 @@ public Stream stream() { }; try (var stream = (Stream) CollectionUtils.toStream(input)) { - var result = stream.collect(toList()); + var result = stream.toList(); assertThat(result).containsExactly("foo", "bar"); } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java index fcd55714f993..798185711c3c 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ExceptionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java index dd7c44d6f770..ee74043ceb39 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/FunctionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java index 917e7c864705..2762de3ee4be 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/LruCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java index c3800cf0c8c0..83ac9352098b 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ModuleUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java index 397419bba54f..801500ccede9 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/PackageUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java index 5b97851f697e..73e35ff00ee9 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/PreconditionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java index 394c7f73aaa9..6b89d7aaeb1d 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,16 +13,16 @@ import static java.time.Duration.ofMillis; import static java.util.Arrays.stream; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.platform.commons.function.Try.success; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; import static org.junit.platform.commons.util.ReflectionUtils.findFields; @@ -67,6 +67,7 @@ import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; import org.junit.platform.commons.logging.LogRecordListener; +import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.ClassWithNestedClasses.Nested1; import org.junit.platform.commons.util.ReflectionUtilsTests.NestedClassTests.ClassWithNestedClasses.Nested2; @@ -284,6 +285,23 @@ void getInterfaceMethodIfPossible() throws Exception { assertThat(interfaceMethod.getDeclaringClass()).isEqualTo(Closeable.class); } + @Test + void isRecordObject() { + assertTrue(ReflectionUtils.isRecordObject(new SomeRecord(1))); + assertFalse(ReflectionUtils.isRecordObject(new ClassWithOneCustomConstructor(""))); + assertFalse(ReflectionUtils.isRecordObject(null)); + } + + @Test + void isRecordClass() { + assertTrue(ReflectionUtils.isRecordClass(SomeRecord.class)); + assertFalse(ReflectionUtils.isRecordClass(ClassWithOneCustomConstructor.class)); + assertFalse(ReflectionUtils.isRecordClass(Object.class)); + } + + record SomeRecord(int n) { + } + static class ClassWithVoidAndNonVoidMethods { void voidMethod() { @@ -560,6 +578,9 @@ void isAssignableTo() { assertTrue(ReflectionUtils.isAssignableTo(Integer.class, int.class)); assertTrue(ReflectionUtils.isAssignableTo(Boolean.class, boolean.class)); + // Void to void + assertFalse(ReflectionUtils.isAssignableTo(Void.class, void.class)); + // Widening Conversions from Wrappers to Primitives assertTrue(ReflectionUtils.isAssignableTo(Integer.class, long.class)); assertTrue(ReflectionUtils.isAssignableTo(Float.class, double.class)); @@ -709,6 +730,47 @@ private void privateMethod() { } + @Nested + class ResourceLoadingTests { + + @Test + void tryToGetResourcePreconditions() { + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetResources("")); + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetResources(" ")); + + assertThrows(PreconditionViolationException.class, () -> ReflectionUtils.tryToGetResources(null)); + assertThrows(PreconditionViolationException.class, + () -> ReflectionUtils.tryToGetResources("org/junit/platform/commons/example.resource", null)); + } + + @Test + void tryToGetResource() { + var tryToGetResource = ReflectionUtils.tryToGetResources("org/junit/platform/commons/example.resource"); + var resource = assertDoesNotThrow(tryToGetResource::get); + assertAll( // + () -> assertThat(resource).hasSize(1), // + () -> assertThat(resource).extracting(Resource::getName) // + .containsExactly("org/junit/platform/commons/example.resource")); + } + + @Test + void tryToGetResourceWithPrefixedSlash() { + var tryToGetResource = ReflectionUtils.tryToGetResources("/org/junit/platform/commons/example.resource"); + var resource = assertDoesNotThrow(tryToGetResource::get); + assertAll( // + () -> assertThat(resource).hasSize(1), // + () -> assertThat(resource).extracting(Resource::getName) // + .containsExactly("org/junit/platform/commons/example.resource")); + } + + @Test + void tryToGetResourceWhenResourceNotFound() { + var tryToGetResource = ReflectionUtils.tryToGetResources("org/junit/platform/commons/no-such.resource"); + var resource = assertDoesNotThrow(tryToGetResource::get); + assertThat(resource).isEmpty(); + } + } + @Nested class ClassLoadingTests { @@ -758,71 +820,132 @@ void loadClass() { @Test void tryToLoadClass() { - assertThat(ReflectionUtils.tryToLoadClass(Integer.class.getName())).isEqualTo(success(Integer.class)); + assertTryToLoadClass(getClass().getName(), getClass()); + assertTryToLoadClass(Integer.class.getName(), Integer.class); + assertTryToLoadClass(Void.class.getName(), Void.class); } @Test void tryToLoadClassTrimsClassName() { - assertThat(ReflectionUtils.tryToLoadClass(" " + Integer.class.getName() + "\t"))// - .isEqualTo(success(Integer.class)); + assertTryToLoadClass(" " + Integer.class.getName() + "\t", Integer.class); } @Test - void tryToLoadClassForPrimitive() { - assertThat(ReflectionUtils.tryToLoadClass(int.class.getName())).isEqualTo(success(int.class)); + void tryToLoadClassForVoidPseudoPrimitiveType() { + assertTryToLoadClass("void", void.class); } @Test - void tryToLoadClassForPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[].class.getName())).isEqualTo(success(int[].class)); + void tryToLoadClassForPrimitiveType() { + assertTryToLoadClass("boolean", boolean.class); + assertTryToLoadClass("char", char.class); + assertTryToLoadClass("byte", byte.class); + assertTryToLoadClass("short", short.class); + assertTryToLoadClass("int", int.class); + assertTryToLoadClass("long", long.class); + assertTryToLoadClass("float", float.class); + assertTryToLoadClass("double", double.class); } @Test - void tryToLoadClassForPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[]")).isEqualTo(success(int[].class)); + void tryToLoadClassForBinaryPrimitiveArrayName() { + assertTryToLoadClass("[Z", boolean[].class); + assertTryToLoadClass("[C", char[].class); + assertTryToLoadClass("[B", byte[].class); + assertTryToLoadClass("[S", short[].class); + assertTryToLoadClass("[I", int[].class); + assertTryToLoadClass("[J", long[].class); + assertTryToLoadClass("[F", float[].class); + assertTryToLoadClass("[D", double[].class); } @Test - void tryToLoadClassForObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[].class.getName())).isEqualTo(success(String[].class)); + void tryToLoadClassForCanonicalPrimitiveArrayName() { + assertTryToLoadClass("boolean[]", boolean[].class); + assertTryToLoadClass("char[]", char[].class); + assertTryToLoadClass("byte[]", byte[].class); + assertTryToLoadClass("short[]", short[].class); + assertTryToLoadClass("int[]", int[].class); + assertTryToLoadClass("long[]", long[].class); + assertTryToLoadClass("float[]", float[].class); + assertTryToLoadClass("double[]", double[].class); } @Test - void tryToLoadClassForObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[]")).isEqualTo(success(String[].class)); + void tryToLoadClassForBinaryObjectArrayName() { + assertTryToLoadClass(String[].class.getName(), String[].class); } @Test - void tryToLoadClassForTwoDimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][].class.getName())).isEqualTo(success(int[][].class)); + void tryToLoadClassForCanonicalObjectArrayName() { + assertTryToLoadClass("java.lang.String[]", String[].class); } @Test - void tryToLoadClassForTwoDimensionaldimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][]")).isEqualTo(success(int[][].class)); + void tryToLoadClassForBinaryTwoDimensionalPrimitiveArrayName() { + assertTryToLoadClass("[[Z", boolean[][].class); + assertTryToLoadClass("[[C", char[][].class); + assertTryToLoadClass("[[B", byte[][].class); + assertTryToLoadClass("[[S", short[][].class); + assertTryToLoadClass("[[I", int[][].class); + assertTryToLoadClass("[[J", long[][].class); + assertTryToLoadClass("[[F", float[][].class); + assertTryToLoadClass("[[D", double[][].class); } @Test - void tryToLoadClassForMultidimensionalPrimitiveArray() { - assertThat(ReflectionUtils.tryToLoadClass(int[][][][][].class.getName()))// - .isEqualTo(success(int[][][][][].class)); + void tryToLoadClassForCanonicalTwoDimensionalPrimitiveArrayName() { + assertTryToLoadClass("boolean[][]", boolean[][].class); + assertTryToLoadClass("char[][]", char[][].class); + assertTryToLoadClass("byte[][]", byte[][].class); + assertTryToLoadClass("short[][]", short[][].class); + assertTryToLoadClass("int[][]", int[][].class); + assertTryToLoadClass("long[][]", long[][].class); + assertTryToLoadClass("float[][]", float[][].class); + assertTryToLoadClass("double[][]", double[][].class); } @Test - void tryToLoadClassForMultidimensionalPrimitiveArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("int[][][][][]")).isEqualTo(success(int[][][][][].class)); + void tryToLoadClassForBinaryMultidimensionalPrimitiveArrayName() { + assertTryToLoadClass("[[[[[Z", boolean[][][][][].class); + assertTryToLoadClass("[[[[[C", char[][][][][].class); + assertTryToLoadClass("[[[[[B", byte[][][][][].class); + assertTryToLoadClass("[[[[[S", short[][][][][].class); + assertTryToLoadClass("[[[[[I", int[][][][][].class); + assertTryToLoadClass("[[[[[J", long[][][][][].class); + assertTryToLoadClass("[[[[[F", float[][][][][].class); + assertTryToLoadClass("[[[[[D", double[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalObjectArray() { - assertThat(ReflectionUtils.tryToLoadClass(String[][][][][].class.getName()))// - .isEqualTo(success(String[][][][][].class)); + void tryToLoadClassForCanonicalMultidimensionalPrimitiveArrayName() { + assertTryToLoadClass("boolean[][][][][]", boolean[][][][][].class); + assertTryToLoadClass("char[][][][][]", char[][][][][].class); + assertTryToLoadClass("byte[][][][][]", byte[][][][][].class); + assertTryToLoadClass("short[][][][][]", short[][][][][].class); + assertTryToLoadClass("int[][][][][]", int[][][][][].class); + assertTryToLoadClass("long[][][][][]", long[][][][][].class); + assertTryToLoadClass("float[][][][][]", float[][][][][].class); + assertTryToLoadClass("double[][][][][]", double[][][][][].class); } @Test - void tryToLoadClassForMultidimensionalObjectArrayUsingSourceCodeSyntax() { - assertThat(ReflectionUtils.tryToLoadClass("java.lang.String[][][][][]"))// - .isEqualTo(success(String[][][][][].class)); + void tryToLoadClassForBinaryMultidimensionalObjectArrayName() { + assertTryToLoadClass(String[][][][][].class.getName(), String[][][][][].class); + } + + @Test + void tryToLoadClassForCanonicalMultidimensionalObjectArrayName() { + assertTryToLoadClass("java.lang.String[][][][][]", String[][][][][].class); + } + + private static void assertTryToLoadClass(String name, Class type) { + try { + assertThat(ReflectionUtils.tryToLoadClass(name).get()).as(name).isEqualTo(type); + } + catch (Exception ex) { + ExceptionUtils.throwAsUncheckedException(ex); + } } } @@ -1550,7 +1673,6 @@ void findMethodsWithoutStaticHidingUsingHierarchyDownMode() throws Exception { var methods = findMethods(child, method -> true, TOP_DOWN); assertEquals(9, methods.size()); - methods.forEach(System.err::println); assertThat(methods.subList(0, 2)).containsOnly(ifcMethod1, ifcMethod2); assertThat(methods.subList(2, 6)).containsOnly(parentMethod1, parentMethod2, parentMethod4, parentMethod5); assertThat(methods.subList(6, 9)).containsOnly(childMethod1, childMethod4, childMethod5); @@ -1642,7 +1764,7 @@ private static List signaturesOf(List methods) { // @formatter:off return methods.stream() .map(m -> String.format("%s(%s)", m.getName(), ClassUtils.nullSafeToString(m.getParameterTypes()))) - .collect(toList()); + .toList(); // @formatter:on } diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java index 75fbe7376782..4e56c9283792 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsWithGenericTypeHierarchiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java index 0367fcdcbf72..d0c5d30ed8de 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/RuntimeUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java b/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java index deaf5e207f9d..42ac1d444034 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/SerializationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java index c90dbd58c32b..ca66fe79c766 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/StringUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java index ddf167ff89d3..c030ba8673de 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/ToStringBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java index 18367db12db0..09c7d84bccbb 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AExecutionConditionClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java index 319fb326daa6..e109392c26c3 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/ATestExecutionListenerClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java index 1cd156254f05..83d2fc6e6205 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/AVanillaEmpty.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java index 3767c86e9178..e8d05459f058 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BExecutionConditionClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java index dd80bad88608..8b57fba484ff 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BTestExecutionListenerClass.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java index b5e683adb975..5c9555799a5e 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/BVanillaEmpty.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/CustomType.java b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/CustomType.java index 3088ef0dda2a..cb08d101527c 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/classes/CustomType.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/classes/CustomType.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java index e35f513135e1..3d29caa3abc8 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/ClassLevelDir.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java index a8f31bb10a2d..c59966885311 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/InstanceLevelDir.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java index 493a8a2bbc24..f5d579ac3f39 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateBeforeMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java index 778076cf14d2..eccfc241a6f8 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/SuperclassWithStaticPackagePrivateTempDirField.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java index 0cc671a42b28..0ed8b8ad1212 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateBeforeMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java index a6749d41c917..1c6efa40cdfb 100644 --- a/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java +++ b/platform-tests/src/test/java/org/junit/platform/commons/util/pkg1/subpkg/SubclassWithNonStaticPackagePrivateTempDirField.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java index b40aa222c0ec..b908ab457893 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleDetailsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,7 +17,8 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; import static org.junit.jupiter.api.DynamicTest.dynamicTest; -import static org.junit.platform.commons.util.ReflectionUtils.findMethods; +import static org.junit.platform.commons.support.HierarchyTraversalMode.TOP_DOWN; +import static org.junit.platform.commons.support.ReflectionSupport.findMethods; import static org.junit.platform.commons.util.ReflectionUtils.getFullyQualifiedMethodName; import java.io.File; @@ -78,7 +79,7 @@ private List scanContainerClassAndCreateDynamicTests(Class conta // String containerName = containerClass.getSimpleName(); List nodes = new ArrayList<>(); Map> map = new EnumMap<>(Details.class); - for (var method : findMethods(containerClass, m -> m.isAnnotationPresent(Test.class))) { + for (var method : findMethods(containerClass, m -> m.isAnnotationPresent(Test.class), TOP_DOWN)) { var methodName = method.getName(); var types = method.getParameterTypes(); for (var details : Details.values()) { @@ -108,6 +109,7 @@ private List scanContainerClassAndCreateDynamicTests(Class conta return nodes; } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("Basic") static class BasicTestCase { @@ -122,6 +124,7 @@ void changeDisplayName() { } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("Skip") static class SkipTestCase { @@ -137,6 +140,7 @@ void skipWithMultiLineMessage() { } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("Fail") static class FailTestCase { @@ -152,6 +156,7 @@ void failWithMultiLineMessage() { } + @SuppressWarnings("JUnitMalformedDeclaration") @DisplayName("Report") static class ReportTestCase { diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java index c3338dfe6753..a9f0be73a810 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -62,6 +62,19 @@ void executeWithExcludeClassnameOptionExcludesClasses(final String line) { ); } + @Test + void executeWithExcludeMethodNameOptionExcludesMethods() { + var line = "execute -e junit-jupiter -p org.junit.platform.console.subpackage --exclude-methodname" + + " ^org\\.junit\\.platform\\.console\\.subpackage\\..+#test"; + var args = line.split(" "); + var result = new ConsoleLauncherWrapper().execute(args); + assertAll("all subpackage test methods are excluded by the method name filter", // + () -> assertArrayEquals(args, result.args), // + () -> assertEquals(0, result.code), // + () -> assertEquals(0, result.getTestsFoundCount()) // + ); + } + @ParameterizedTest @ValueSource(strings = { // "-e junit-jupiter -o java.base", "-e junit-jupiter --select-module java.base", // diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java index ef30a6d3db59..82be8c8a584a 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,7 +22,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.EmptySource; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.console.tasks.ConsoleTestExecutor; /** * @since 1.0 @@ -36,8 +35,7 @@ class ConsoleLauncherTests { @EmptySource @MethodSource("commandsWithEmptyOptionExitCodes") void displayHelp(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--help").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--help").getExitCode(); assertEquals(0, exitCode); assertThat(output()).contains("--help"); @@ -47,8 +45,7 @@ void displayHelp(String command) { @EmptySource @MethodSource("commandsWithEmptyOptionExitCodes") void displayVersion(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--version").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--version").getExitCode(); assertEquals(0, exitCode); assertThat(output()).contains("JUnit Platform Console Launcher"); @@ -57,8 +54,7 @@ void displayVersion(String command) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void displayBanner(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - consoleLauncher.run(command); + ConsoleLauncher.run(printSink, printSink, command); assertThat(output()).contains("Thanks for using JUnit!"); } @@ -66,8 +62,7 @@ void displayBanner(String command) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void disableBanner(String command, int expectedExitCode) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--disable-banner").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--disable-banner").getExitCode(); assertEquals(expectedExitCode, exitCode); assertThat(output()).doesNotContain("Thanks for using JUnit!"); @@ -76,8 +71,7 @@ void disableBanner(String command, int expectedExitCode) { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void executeWithUnknownCommandLineOption(String command) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var exitCode = consoleLauncher.run(command, "--all").getExitCode(); + var exitCode = ConsoleLauncher.run(printSink, printSink, command, "--all").getExitCode(); assertEquals(-1, exitCode); assertThat(output()).contains("Unknown option: '--all'").contains("Usage:"); @@ -90,8 +84,7 @@ private String output() { @ParameterizedTest(name = "{0}") @MethodSource("commandsWithEmptyOptionExitCodes") void executeWithoutCommandLineOptions(String command, int expectedExitCode) { - var consoleLauncher = new ConsoleLauncher(ConsoleTestExecutor::new, printSink, printSink); - var actualExitCode = consoleLauncher.run(command).getExitCode(); + var actualExitCode = ConsoleLauncher.run(printSink, printSink, command).getExitCode(); assertEquals(expectedExitCode, actualExitCode); } diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java index 7ae6ba8fd921..03fb3528f543 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,6 +17,7 @@ import java.io.StringWriter; import java.util.Optional; +import org.junit.platform.console.options.CommandFacade; import org.junit.platform.console.tasks.ConsoleTestExecutor; /** @@ -26,16 +27,14 @@ class ConsoleLauncherWrapper { private final StringWriter out = new StringWriter(); private final StringWriter err = new StringWriter(); - private final ConsoleLauncher consoleLauncher; + private final ConsoleTestExecutor.Factory consoleTestExecutorFactory; ConsoleLauncherWrapper() { this(ConsoleTestExecutor::new); } private ConsoleLauncherWrapper(ConsoleTestExecutor.Factory consoleTestExecutorFactory) { - var outWriter = new PrintWriter(out, false); - var errWriter = new PrintWriter(err, false); - this.consoleLauncher = new ConsoleLauncher(consoleTestExecutorFactory, outWriter, errWriter); + this.consoleTestExecutorFactory = consoleTestExecutorFactory; } public ConsoleLauncherWrapperResult execute(String... args) { @@ -47,7 +46,9 @@ public ConsoleLauncherWrapperResult execute(int expectedExitCode, String... args } public ConsoleLauncherWrapperResult execute(Optional expectedCode, String... args) { - var result = consoleLauncher.run(args); + var outWriter = new PrintWriter(out, false); + var errWriter = new PrintWriter(err, false); + var result = new CommandFacade(consoleTestExecutorFactory).run(args, outWriter, errWriter); var code = result.getExitCode(); var outText = out.toString(); var errText = err.toString(); diff --git a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java index d0efb56e0e35..f7fd34632fe5 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java +++ b/platform-tests/src/test/java/org/junit/platform/console/ConsoleLauncherWrapperResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java b/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java index d34740b1b0d6..0745f27a914a 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/options/CommandLineOptionsParsingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -26,6 +26,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectModule; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUri; import java.io.File; @@ -44,6 +45,7 @@ import org.junit.jupiter.params.provider.EnumSource; import org.junit.platform.engine.DiscoverySelector; import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.engine.discovery.FilePosition; /** * @since 1.10 @@ -64,6 +66,8 @@ void parseNoArguments() { () -> assertEquals(List.of(), options.discovery.getExcludedClassNamePatterns()), () -> assertEquals(List.of(), options.discovery.getIncludedPackages()), () -> assertEquals(List.of(), options.discovery.getExcludedPackages()), + () -> assertEquals(List.of(), options.discovery.getIncludedMethodNamePatterns()), + () -> assertEquals(List.of(), options.discovery.getExcludedMethodNamePatterns()), () -> assertEquals(List.of(), options.discovery.getIncludedTagExpressions()), () -> assertEquals(List.of(), options.discovery.getExcludedTagExpressions()), () -> assertEquals(List.of(), options.discovery.getAdditionalClasspathEntries()), @@ -198,6 +202,46 @@ void parseValidExcludedPackages(ArgsType type) { // @formatter:on } + @ParameterizedTest + @EnumSource + void parseValidIncludeMethodNamePatterns(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(".+#method.*"), + type.parseArgLine("--include-methodname .+#method.*").discovery.getIncludedMethodNamePatterns()), + () -> assertEquals(List.of(".+#methodA.*", ".+#methodB.*"), + type.parseArgLine("--include-methodname .+#methodA.* --include-methodname .+#methodB.*").discovery.getIncludedMethodNamePatterns()), + () -> assertEquals(List.of(".+#method.*"), + type.parseArgLine("--include-methodname=.+#method.*").discovery.getIncludedMethodNamePatterns()) + ); + // @formatter:on + } + + @ParameterizedTest + @EnumSource + void parseValidExcludeMethodNamePatterns(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(".+#method.*"), + type.parseArgLine("--exclude-methodname .+#method.*").discovery.getExcludedMethodNamePatterns()), + () -> assertEquals(List.of(".+#methodA.*", ".+#methodB.*"), + type.parseArgLine("--exclude-methodname .+#methodA.* --exclude-methodname .+#methodB.*").discovery.getExcludedMethodNamePatterns()), + () -> assertEquals(List.of(".+#method.*"), + type.parseArgLine("--exclude-methodname=.+#method.*").discovery.getExcludedMethodNamePatterns()) + ); + // @formatter:on + } + + @Test + void parseInvalidIncludeMethodNamePatterns() { + assertOptionWithMissingRequiredArgumentThrowsException("--include-methodname"); + } + + @Test + void parseInvalidExcludeMethodNamePatterns() { + assertOptionWithMissingRequiredArgumentThrowsException("--exclude-methodname"); + } + @ParameterizedTest @EnumSource void parseValidIncludedTags(ArgsType type) { @@ -319,7 +363,8 @@ void parseValidUriSelectors(ArgsType type) { () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("-select-uri=file:///foo.txt").discovery.getSelectedUris()), () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri file:///foo.txt").discovery.getSelectedUris()), () -> assertEquals(List.of(selectUri("file:///foo.txt")), type.parseArgLine("--select-uri=file:///foo.txt").discovery.getSelectedUris()), - () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt -u https://example").discovery.getSelectedUris()) + () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt -u https://example").discovery.getSelectedUris()), + () -> assertEquals(List.of(selectUri("file:///foo.txt"), selectUri("https://example")), type.parseArgLine("-u file:///foo.txt https://example").discovery.getSelectedUris()) ); // @formatter:on } @@ -340,7 +385,10 @@ void parseValidFileSelectors(ArgsType type) { () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("-select-file=foo.txt").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file foo.txt").discovery.getSelectedFiles()), () -> assertEquals(List.of(selectFile("foo.txt")), type.parseArgLine("--select-file=foo.txt").discovery.getSelectedFiles()), - () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt -f bar.csv").discovery.getSelectedFiles()) + () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt -f bar.csv").discovery.getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt"), selectFile("bar.csv")), type.parseArgLine("-f foo.txt bar.csv").discovery.getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt", FilePosition.from(5))), type.parseArgLine("-f foo.txt?line=5").discovery.getSelectedFiles()), + () -> assertEquals(List.of(selectFile("foo.txt", FilePosition.from(12, 34))), type.parseArgLine("-f foo.txt?line=12&column=34").discovery.getSelectedFiles()) ); // @formatter:on } @@ -361,7 +409,8 @@ void parseValidDirectorySelectors(ArgsType type) { () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("-select-directory=foo/bar").discovery.getSelectedDirectories()), () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory foo/bar").discovery.getSelectedDirectories()), () -> assertEquals(List.of(selectDirectory("foo/bar")), type.parseArgLine("--select-directory=foo/bar").discovery.getSelectedDirectories()), - () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar -d bar/qux").discovery.getSelectedDirectories()) + () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar -d bar/qux").discovery.getSelectedDirectories()), + () -> assertEquals(List.of(selectDirectory("foo/bar"), selectDirectory("bar/qux")), type.parseArgLine("-d foo/bar bar/qux").discovery.getSelectedDirectories()) ); // @formatter:on } @@ -382,7 +431,8 @@ void parseValidModuleSelectors(ArgsType type) { () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("-select-module=com.acme.foo").discovery.getSelectedModules()), () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module com.acme.foo").discovery.getSelectedModules()), () -> assertEquals(List.of(selectModule("com.acme.foo")), type.parseArgLine("--select-module=com.acme.foo").discovery.getSelectedModules()), - () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo -o com.example.bar").discovery.getSelectedModules()) + () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo -o com.example.bar").discovery.getSelectedModules()), + () -> assertEquals(List.of(selectModule("com.acme.foo"), selectModule("com.example.bar")), type.parseArgLine("-o com.acme.foo com.example.bar").discovery.getSelectedModules()) ); // @formatter:on } @@ -403,7 +453,8 @@ void parseValidPackageSelectors(ArgsType type) { () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("-select-package=com.acme.foo").discovery.getSelectedPackages()), () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package com.acme.foo").discovery.getSelectedPackages()), () -> assertEquals(List.of(selectPackage("com.acme.foo")), type.parseArgLine("--select-package=com.acme.foo").discovery.getSelectedPackages()), - () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo -p com.example.bar").discovery.getSelectedPackages()) + () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo -p com.example.bar").discovery.getSelectedPackages()), + () -> assertEquals(List.of(selectPackage("com.acme.foo"), selectPackage("com.example.bar")), type.parseArgLine("-p com.acme.foo com.example.bar").discovery.getSelectedPackages()) ); // @formatter:on } @@ -424,7 +475,8 @@ void parseValidClassSelectors(ArgsType type) { () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("-select-class=com.acme.Foo").discovery.getSelectedClasses()), () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class com.acme.Foo").discovery.getSelectedClasses()), () -> assertEquals(List.of(selectClass("com.acme.Foo")), type.parseArgLine("--select-class=com.acme.Foo").discovery.getSelectedClasses()), - () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo -c com.example.Bar").discovery.getSelectedClasses()) + () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo -c com.example.Bar").discovery.getSelectedClasses()), + () -> assertEquals(List.of(selectClass("com.acme.Foo"), selectClass("com.example.Bar")), type.parseArgLine("-c com.acme.Foo com.example.Bar").discovery.getSelectedClasses()) ); // @formatter:on } @@ -446,7 +498,9 @@ void parseValidMethodSelectors(ArgsType type) { () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method com.acme.Foo#m()").discovery.getSelectedMethods()), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()")), type.parseArgLine("--select-method=com.acme.Foo#m()").discovery.getSelectedMethods()), () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)")), - type.parseArgLine("-m com.acme.Foo#m() -m com.example.Bar#method(java.lang.Object)").discovery.getSelectedMethods()) + type.parseArgLine("-m com.acme.Foo#m() -m com.example.Bar#method(java.lang.Object)").discovery.getSelectedMethods()), + () -> assertEquals(List.of(selectMethod("com.acme.Foo#m()"), selectMethod("com.example.Bar#method(java.lang.Object)")), + type.parseArgLine("-m com.acme.Foo#m() com.example.Bar#method(java.lang.Object)").discovery.getSelectedMethods()) ); // @formatter:on } @@ -467,7 +521,10 @@ void parseValidClasspathResourceSelectors(ArgsType type) { () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("-select-resource=/foo.csv").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource /foo.csv").discovery.getSelectedClasspathResources()), () -> assertEquals(List.of(selectClasspathResource("/foo.csv")), type.parseArgLine("--select-resource=/foo.csv").discovery.getSelectedClasspathResources()), - () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv -r bar.json").discovery.getSelectedClasspathResources()) + () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv -r bar.json").discovery.getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv"), selectClasspathResource("bar.json")), type.parseArgLine("-r /foo.csv bar.json").discovery.getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv", FilePosition.from(5))), type.parseArgLine("-r /foo.csv?line=5").discovery.getSelectedClasspathResources()), + () -> assertEquals(List.of(selectClasspathResource("/foo.csv", FilePosition.from(12, 34))), type.parseArgLine("-r /foo.csv?line=12&column=34").discovery.getSelectedClasspathResources()) ); // @formatter:on } @@ -488,7 +545,8 @@ void parseValidIterationSelectors(ArgsType type) { () -> assertEquals(List.of(selectIteration(selectPackage("com.acme.foo"), 3)), type.parseArgLine("-select-iteration=package:com.acme.foo[3]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectModule("com.acme.foo"), 0, 1, 2, 4, 5, 6)), type.parseArgLine("--select-iteration module:com.acme.foo[0..2,4..6]").discovery.getSelectedIterations()), () -> assertEquals(List.of(selectIteration(selectDirectory("foo/bar"), 1, 5)), type.parseArgLine("--select-iteration=directory:foo/bar[1,5]").discovery.getSelectedIterations()), - () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] -i uri:file:///foo.txt[7]").discovery.getSelectedIterations()) + () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] -i uri:file:///foo.txt[7]").discovery.getSelectedIterations()), + () -> assertEquals(List.of(selectIteration(selectFile("foo.txt"), 6), selectIteration(selectUri("file:///foo.txt"), 7)), type.parseArgLine("-i file:foo.txt[6] uri:file:///foo.txt[7]").discovery.getSelectedIterations()) ); // @formatter:on } @@ -498,6 +556,24 @@ void parseInvalidIterationSelectors() { assertOptionWithMissingRequiredArgumentThrowsException("-i", "--select-iteration"); } + @ParameterizedTest + @EnumSource + void parseValidUniqueIdSelectors(ArgsType type) { + // @formatter:off + assertAll( + () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")), type.parseArgLine("--uid [engine:junit-jupiter]/[class:MyClass]/[method:myMethod]").discovery.getSelectedUniqueIds()), + () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")), type.parseArgLine("--select-unique-id [engine:junit-jupiter]/[class:MyClass]/[method:myMethod]").discovery.getSelectedUniqueIds()), + () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass1]"), selectUniqueId("[engine:junit-jupiter]/[class:MyClass2]")), type.parseArgLine("--uid [engine:junit-jupiter]/[class:MyClass1] --uid [engine:junit-jupiter]/[class:MyClass2]").discovery.getSelectedUniqueIds()), + () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass1]"), selectUniqueId("[engine:junit-jupiter]/[class:MyClass2]")), type.parseArgLine("--uid [engine:junit-jupiter]/[class:MyClass1] [engine:junit-jupiter]/[class:MyClass2]").discovery.getSelectedUniqueIds()) + ); + // @formatter:on + } + + @Test + void parseInvalidUniqueIdSelectors() { + assertOptionWithMissingRequiredArgumentThrowsException("--uid", "--select-unique-id"); + } + @ParameterizedTest @EnumSource void parseClasspathScanningEntries(ArgsType type) { @@ -580,7 +656,8 @@ void parseValidSelectorIdentifier(ArgsType type) { () -> assertEquals(List.of(selectPackage("com.acme.foo")), parseIdentifiers(type,"--select package:com.acme.foo")), () -> assertEquals(List.of(selectModule("com.acme.foo")), parseIdentifiers(type,"--select module:com.acme.foo")), () -> assertEquals(List.of(selectDirectory("foo/bar")), parseIdentifiers(type,"--select directory:foo/bar")), - () -> assertEquals(List.of(selectFile("foo.txt"), selectUri("file:///foo.txt")), parseIdentifiers(type,"--select file:foo.txt --select uri:file:///foo.txt")) + () -> assertEquals(List.of(selectFile("foo.txt"), selectUri("file:///foo.txt")), parseIdentifiers(type,"--select file:foo.txt --select uri:file:///foo.txt")), + () -> assertEquals(List.of(selectUniqueId("[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")), parseIdentifiers(type,"--select uid:[engine:junit-jupiter]/[class:MyClass]/[method:myMethod]")) ); // @formatter:on } diff --git a/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java index b322c1657afd..9fb2d92da4c0 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/options/ConsoleUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java b/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java index cddf92b68611..d7186357b51c 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/options/ExecuteTestsCommandTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java b/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java index cb49cbf631f4..e3d0cdcd1390 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/options/ThemeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java index 287cbbc35005..98f28f17639f 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java index bd7d2accebc1..2147563c291f 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForInnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java index ecbe48c2e587..5a47e143d8c8 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ // problem since the test method here can never fail. class ContainerForStaticNestedTest { + @SuppressWarnings("JUnitMalformedDeclaration") static class NestedTest { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java index 9f51d250dee0..c9febd321bf8 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ContainerForStaticNestedTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ // problem since the test method here can never fail. class ContainerForStaticNestedTests { + @SuppressWarnings("JUnitMalformedDeclaration") static class NestedTests { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java index cdc962fe84d0..21d85cc79860 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/SecondTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java index cf5d40fd7afe..1e80f0c98650 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java index af3e80934eab..82a75f8b9b5b 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/Test1.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java index 8cb8a8c507fc..3e3869c7fe30 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java index 88fec97e3acd..66f87788ddbf 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/subpackage/ThirdTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java index fce602b53011..55adbe486218 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/ColorPaletteTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.io.PrintWriter; @@ -186,7 +187,7 @@ void flat_single_color() { private void demoTestRun(TestExecutionListener listener) { TestDescriptor testDescriptor = new TestDescriptorStub(UniqueId.forEngine("demo-engine"), "My Test"); - TestPlan testPlan = TestPlan.from(List.of(testDescriptor), mock()); + TestPlan testPlan = TestPlan.from(List.of(testDescriptor), mock(), dummyOutputDirectoryProvider()); listener.testPlanExecutionStarted(testPlan); listener.executionStarted(TestIdentifier.from(testDescriptor)); listener.executionFinished(TestIdentifier.from(testDescriptor), TestExecutionResult.successful()); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java index 9f04b044f370..f3c99037a180 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/ConsoleTestExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java index a3b72bb4692c..0c0d6e9ec935 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/CustomContextClassLoaderExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java index b8eea36cfd1e..014c167a203e 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/DiscoveryRequestCreatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -178,6 +178,18 @@ void convertsPackageOptions() { assertExcludes(packageNameFilters.get(1), "org.junit.excluded1"); } + @Test + void convertsMethodNamePatternOptions() { + options.setScanClasspath(true); + options.setIncludedMethodNamePatterns(List.of(".+#foo.*Bar", ".+#toString", ".+#method.*")); + options.setExcludedMethodNamePatterns(List.of(".+#bar.*Foo")); + var request = convert(); + var methodNameFilters = request.getPostDiscoveryFilters(); + assertThat(methodNameFilters).hasSize(2); + assertThat(methodNameFilters.get(0).toString()).contains(".+#foo.*Bar", ".+#toString", ".+#method.*"); + assertThat(methodNameFilters.get(1).toString()).contains(".+#bar.*Foo"); + } + @Test void convertsTagOptions() { options.setScanClasspath(true); @@ -347,8 +359,7 @@ void convertsConfigurationParametersResources() { } private LauncherDiscoveryRequest convert() { - var creator = new DiscoveryRequestCreator(); - return creator.toDiscoveryRequest(options); + return DiscoveryRequestCreator.toDiscoveryRequestBuilder(options).build(); } private void assertIncludes(Filter filter, String included) { diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java index 8b0c7a5a29b4..4ca4605f6e35 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/FlatPrintingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,11 +18,13 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.Path; import org.assertj.core.util.Maps; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; @@ -60,6 +62,20 @@ void reportingEntryPublished() { () -> assertTrue(lines[1].endsWith(", foo = 'bar']"))); } + @Test + void fileEntryPublished() { + var stringWriter = new StringWriter(); + listener(stringWriter).fileEntryPublished(newTestIdentifier(), + FileEntry.from(Path.of("test.txt"), "text/plain")); + var lines = lines(stringWriter); + + assertEquals(2, lines.length); + assertAll("lines in the output", // + () -> assertEquals("Reported: demo-test ([engine:demo-engine])", lines[0]), // + () -> assertTrue(lines[1].startsWith(INDENTATION + "=> Reported file: FileEntry [timestamp =")), // + () -> assertTrue(lines[1].endsWith(", path = test.txt, mediaType = 'text/plain']"))); + } + @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java index c6443a624f4e..35c37df1c1cc 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/TestFeedPrintingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.io.PrintWriter; @@ -44,7 +45,7 @@ void prepareListener() { "%c ool test"); engineDescriptor.addChild(testDescriptor); - testPlan = TestPlan.from(Collections.singleton(engineDescriptor), mock()); + testPlan = TestPlan.from(Collections.singleton(engineDescriptor), mock(), dummyOutputDirectoryProvider()); testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); listener.testPlanExecutionStarted(testPlan); diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java index 93cf99cbcd80..33c08f6063a8 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreeNodeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java index 8316437899d3..b4f712a639f4 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/TreePrinterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,12 +21,14 @@ import java.io.PrintWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.console.options.Theme; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; @@ -92,7 +94,7 @@ void reportsAreTabbedCorrectly() { c1.addChild(m1); var m2 = new TreeNode(identifier("m-2", "method two")).setResult(successful()); - m2.addReportEntry(ReportEntry.from("key", "m-2")); + m2.addFileEntry(FileEntry.from(Path.of("test.txt"), "text/plain")); c1.addChild(m2); new TreePrinter(out, Theme.UNICODE, ColorPalette.NONE).print(root); @@ -105,7 +107,7 @@ void reportsAreTabbedCorrectly() { " ├─ method one ✔", // " │ ....-..-..T..:...* key = `m-1`", // " └─ method two ✔", // - " ....-..-..T..:...* key = `m-2`" // + " ....-..-..T..:...* file:.*" // ), // actual()); } diff --git a/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java b/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreePrintingListenerTests.java similarity index 84% rename from platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java rename to platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreePrintingListenerTests.java index 52c74188e08a..73addec06823 100644 --- a/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreeListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/console/tasks/VerboseTreePrintingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,11 +15,13 @@ import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.platform.console.options.Theme; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.fakes.TestDescriptorStub; import org.junit.platform.launcher.TestIdentifier; @@ -27,7 +29,7 @@ /** * @since 1.3.2 */ -class VerboseTreeListenerTests { +class VerboseTreePrintingListenerTests { private static final String EOL = System.lineSeparator(); @@ -56,6 +58,18 @@ void reportingEntryPublished() { assertLinesMatch(List.of(" reports: ReportEntry \\[timestamp = .+, foo = 'bar'\\]"), List.of(lines)); } + @Test + void fileEntryPublished() { + var stringWriter = new StringWriter(); + listener(stringWriter).fileEntryPublished(newTestIdentifier(), + FileEntry.from(Path.of("test.txt"), "text/plain")); + var lines = lines(stringWriter); + + assertLinesMatch( + List.of(" reports: FileEntry \\[timestamp = .+, path = test.txt, mediaType = 'text/plain'\\]"), + List.of(lines)); + } + @Test void executionFinishedWithFailure() { var stringWriter = new StringWriter(); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java b/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java index 92a7f879e960..e1679ac20b12 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/FilterCompositionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java index 6b8907e463dd..2ce70b95db9d 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/TestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java b/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java index e18b6cc990f9..63adfe5e51a5 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/TestTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java index ee6c731d6643..f2901b85557b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdFormatTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java index c126da921107..d9dc72a328f9 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/UniqueIdTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java index 990c1dc1e179..fd14be421e26 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassNameFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java index 8799c8bf5a89..80dd13652a1a 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClassSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,9 +12,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -23,7 +23,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ClassSelectorTests extends AbstractEqualsAndHashCodeTests { +class ClassSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java index ae696da1de20..bf4222c3f6fd 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathResourceSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link ClasspathResourceSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ClasspathResourceSelectorTests extends AbstractEqualsAndHashCodeTests { +class ClasspathResourceSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java index bfdb69c072e1..1b690029aaf4 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ClasspathRootSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,11 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import java.net.URI; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link ClasspathRootSelector}. @@ -21,7 +22,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ClasspathRootSelectorTests extends AbstractEqualsAndHashCodeTests { +class ClasspathRootSelectorTests { @Test void equalsAndHashCode() throws Exception { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java index 1ae830d99289..5eecbbdfcca5 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DirectorySelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link DirectorySelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class DirectorySelectorTests extends AbstractEqualsAndHashCodeTests { +class DirectorySelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java index 5a1a96506927..c384a90434d2 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/DiscoverySelectorsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.InstanceOfAssertFactories.type; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.engine.discovery.JupiterUniqueIdBuilder.uniqueIdForMethod; @@ -38,6 +39,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -53,6 +55,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.support.Resource; import org.junit.platform.commons.test.TestClassLoader; import org.junit.platform.commons.util.ReflectionUtils; import org.junit.platform.engine.DiscoverySelector; @@ -286,12 +289,22 @@ void parseDirectorySelectorWithAbsolutePath() { } @Test - void selectClasspathResources() { - assertViolatesPrecondition(() -> selectClasspathResource(null)); + void selectClasspathResourcesPreconditions() { + assertViolatesPrecondition(() -> selectClasspathResource((String) null)); assertViolatesPrecondition(() -> selectClasspathResource("")); assertViolatesPrecondition(() -> selectClasspathResource(" ")); assertViolatesPrecondition(() -> selectClasspathResource("\t")); + assertViolatesPrecondition(() -> selectClasspathResource((Set) null)); + assertViolatesPrecondition(() -> selectClasspathResource(Collections.emptySet())); + assertViolatesPrecondition(() -> selectClasspathResource(Collections.singleton(null))); + assertViolatesPrecondition(() -> selectClasspathResource(Set.of(new StubResource(null)))); + assertViolatesPrecondition(() -> selectClasspathResource(Set.of(new StubResource("")))); + assertViolatesPrecondition( + () -> selectClasspathResource(Set.of(new StubResource("a"), new StubResource("b")))); + } + @Test + void selectClasspathResources() { // with unnecessary "/" prefix var selector = selectClasspathResource("/foo/bar/spec.xml"); assertEquals("foo/bar/spec.xml", selector.getClasspathResourceName()); @@ -301,6 +314,23 @@ void selectClasspathResources() { assertEquals("A/B/C/spec.json", selector.getClasspathResourceName()); } + @Test + void getSelectedClasspathResources() { + var selector = selectClasspathResource("org/junit/platform/commons/example.resource"); + var classpathResources = selector.getClasspathResources(); + assertAll(() -> assertThat(classpathResources).hasSize(1), // + () -> assertThat(classpathResources) // + .extracting(Resource::getName) // + .containsExactly("org/junit/platform/commons/example.resource") // + ); + } + + @Test + void getMissingClasspathResources() { + var selector = selectClasspathResource("org/junit/platform/commons/no-such-example.resource"); + assertViolatesPrecondition(selector::getClasspathResources); + } + @Test void selectClasspathResourcesWithFilePosition() { var filePosition = FilePosition.from(12, 34); @@ -359,6 +389,18 @@ void parseClasspathResourcesWithFilePosition() { .containsExactly("A/B/C/spec.json", Optional.of(filePosition)); } + private record StubResource(String name) implements Resource { + + @Override + public String getName() { + return name(); + } + + @Override + public URI getUri() { + return null; + } + } } @Nested diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java index e39acbeedf4d..8108bfd76a36 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FilePositionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; @@ -22,7 +23,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -31,7 +31,7 @@ * @since 1.7 */ @DisplayName("FilePosition unit tests") -class FilePositionTests extends AbstractEqualsAndHashCodeTests { +class FilePositionTests { @Test @DisplayName("factory method preconditions") diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java index 7c167b6bb966..d324fe767504 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/FileSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link FileSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class FileSelectorTests extends AbstractEqualsAndHashCodeTests { +class FileSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/IterationSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/IterationSelectorTests.java index 9eb66c820c2a..cba42d58f00b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/IterationSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/IterationSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java index 21dafc77214a..b8599347ea67 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/MethodSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,11 +12,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.JUnitException; import org.junit.platform.commons.PreconditionViolationException; @@ -26,7 +26,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class MethodSelectorTests extends AbstractEqualsAndHashCodeTests { +class MethodSelectorTests { private static final String TEST_CASE_NAME = TestCase.class.getName(); diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java index 9ea129728212..34cf92a2c4db 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/ModuleSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link ModuleSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class ModuleSelectorTests extends AbstractEqualsAndHashCodeTests { +class ModuleSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java index 4f73cb36ea5f..a06756041174 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedClassSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,11 +12,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.util.List; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -25,7 +25,7 @@ * @since 1.6 * @see DiscoverySelectorsTests */ -class NestedClassSelectorTests extends AbstractEqualsAndHashCodeTests { +class NestedClassSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java index 33235ed769d8..003721ff098e 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/NestedMethodSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,11 +12,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.util.List; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.commons.PreconditionViolationException; /** @@ -25,7 +25,7 @@ * @since 1.6 * @see DiscoverySelectorsTests */ -class NestedMethodSelectorTests extends AbstractEqualsAndHashCodeTests { +class NestedMethodSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java index cedd2ed6f439..b93ba77a34c9 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageNameFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java index cff34011791a..fcc90fd8b2ef 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/PackageSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link PackageSelector}. @@ -19,7 +20,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class PackageSelectorTests extends AbstractEqualsAndHashCodeTests { +class PackageSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java index 98dedf083850..fef5907b6ca3 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UniqueIdSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,8 +10,9 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.engine.UniqueId; /** @@ -20,7 +21,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class UniqueIdSelectorTests extends AbstractEqualsAndHashCodeTests { +class UniqueIdSelectorTests { @Test void equalsAndHashCode() { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java index 76def76ec775..79fafc208768 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/discovery/UriSelectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,11 @@ package org.junit.platform.engine.discovery; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; + import java.net.URI; import org.junit.jupiter.api.Test; -import org.junit.platform.AbstractEqualsAndHashCodeTests; /** * Unit tests for {@link UriSelector}. @@ -21,7 +22,7 @@ * @since 1.3 * @see DiscoverySelectorsTests */ -class UriSelectorTests extends AbstractEqualsAndHashCodeTests { +class UriSelectorTests { @Test void equalsAndHashCode() throws Exception { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java index bf2f411e6b06..ae64f77463d3 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/config/PrefixedConfigurationParametersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java index 3b8c8ae0d5f8..5f4fe3011fb2 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java index 9ce87b9b47f2..6154598e5264 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/AbstractTestSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -25,7 +25,6 @@ import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; -import org.junit.platform.AbstractEqualsAndHashCodeTests; import org.junit.platform.engine.TestSource; /** @@ -34,7 +33,7 @@ * * @since 1.0 */ -abstract class AbstractTestSourceTests extends AbstractEqualsAndHashCodeTests { +abstract class AbstractTestSourceTests { abstract Stream createSerializableInstances() throws Exception; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java index ef8b2b8d9a91..078c9f629c76 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClassSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.io.Serializable; import java.net.URI; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java index 1b861208a4ea..9141a47fd5bb 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/ClasspathResourceSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.platform.engine.support.descriptor.ClasspathResourceSource.CLASSPATH_SCHEME; import java.net.URI; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java index 121000e5c961..2702e480ae33 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/CompositeTestSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.io.File; import java.util.ArrayList; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java index 1fe34b233a90..03f5edd70f6b 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DefaultUriSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.net.URI; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java index 5f4e67153a52..5687846244dc 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoClassTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.platform.engine.support.descriptor; import static java.util.stream.Collectors.toCollection; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.util.LinkedHashSet; import java.util.Set; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java index 85f25606424c..4e3c5aee0aa0 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/DemoMethodTestDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,7 @@ package org.junit.platform.engine.support.descriptor; import static java.util.stream.Collectors.toCollection; -import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; import java.lang.reflect.Method; import java.util.LinkedHashSet; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java index af5562bc8f57..d1d85ae156d7 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FilePositionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java index fd205bc2c6dd..307193a02774 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.io.File; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java index 3b8984829a61..4f97624c7ce6 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/MethodSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.io.Serializable; import java.lang.reflect.Method; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java index c9a752ca5a68..8da7f16a8545 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/PackageSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.EqualsAndHashCodeAssertions.assertEqualsAndHashCode; import java.io.Serializable; import java.util.stream.Stream; diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java b/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java new file mode 100644 index 000000000000..7a1c9b4d072d --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/discovery/ResourceContainerSelectorResolverTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.discovery; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClasspathRoots; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; +import static org.junit.platform.engine.discovery.PackageNameFilter.includePackageNames; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Match.exact; +import static org.junit.platform.engine.support.discovery.SelectorResolver.Resolution.match; + +import java.net.URI; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.support.Resource; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.discovery.ClasspathResourceSelector; +import org.junit.platform.engine.support.descriptor.EngineDescriptor; +import org.junit.platform.fakes.TestDescriptorStub; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; + +class ResourceContainerSelectorResolverTest { + + final TestDescriptor engineDescriptor = new EngineDescriptor(UniqueId.forEngine("resource-engine"), + "Resource Engine"); + final Predicate isResource = resource -> resource.getName().endsWith(".resource"); + + final EngineDiscoveryRequestResolver resolver = EngineDiscoveryRequestResolver.builder() // + .addResourceContainerSelectorResolver(isResource) // + .addSelectorResolver(new ResourceSelectorResolver()) // + .build(); + + @Test + void shouldDiscoverAllResourcesInPackage() { + var request = LauncherDiscoveryRequestBuilder.request() // + .selectors(selectPackage("org.junit.platform.commons")) // + .build(); + + resolver.resolve(request, engineDescriptor); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .containsExactlyInAnyOrder( + "org/junit/platform/commons/example.resource", + "org/junit/platform/commons/other-example.resource"); + // @formatter:on + } + + @Test + void shouldDiscoverAllResourcesInRootPackage() { + var request = LauncherDiscoveryRequestBuilder.request() // + .selectors(selectPackage("")) // + .build(); + + resolver.resolve(request, engineDescriptor); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .containsExactlyInAnyOrder( + "default-package.resource", + "org/junit/platform/commons/example.resource", + "org/junit/platform/commons/other-example.resource"); + // @formatter:on + } + + @Test + void shouldFilterPackages() { + var request = LauncherDiscoveryRequestBuilder.request() // + .selectors(selectPackage("")) // + .filters(includePackageNames("org.junit.platform")) // + .build(); + + resolver.resolve(request, engineDescriptor); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .containsExactlyInAnyOrder( + "org/junit/platform/commons/example.resource", + "org/junit/platform/commons/other-example.resource"); + // @formatter:on + } + + @Test + void shouldDiscoverAllResourcesInClasspathRoot() { + var request = LauncherDiscoveryRequestBuilder.request() // + .selectors(selectClasspathRoots(getTestClasspathResourceRoot())) // + .build(); + + resolver.resolve(request, engineDescriptor); + + // @formatter:off + assertThat(engineDescriptor.getChildren()) + .extracting(TestDescriptor::getDisplayName) + .containsExactlyInAnyOrder( + "default-package.resource", + "org/junit/platform/commons/example.resource", + "org/junit/platform/commons/other-example.resource"); + // @formatter:on + } + + private Set getTestClasspathResourceRoot() { + // Gradle puts classes and resources in different roots. + var defaultPackageResource = "/default-package.resource"; + var resourceUri = getClass().getResource(defaultPackageResource).toString(); + var uri = URI.create(resourceUri.substring(0, resourceUri.length() - defaultPackageResource.length())); + return Collections.singleton(Path.of(uri)); + } + + private static class ResourceSelectorResolver implements SelectorResolver { + @Override + public Resolution resolve(ClasspathResourceSelector selector, Context context) { + return context.addToParent(parent -> createTestDescriptor(parent, selector.getClasspathResourceName())) // + .map(testDescriptor -> match(exact(testDescriptor))) // + .orElseGet(Resolution::unresolved); + } + + private static Optional createTestDescriptor(TestDescriptor parent, + String classpathResourceName) { + var uniqueId = parent.getUniqueId().append("resource", classpathResourceName); + var descriptor = new TestDescriptorStub(uniqueId, classpathResourceName); + return Optional.of(descriptor); + } + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java index 756c710e25c6..3fbbaabf6191 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/CompositeLockTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,9 +19,11 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.Lock; +import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; /** * @since 1.3 @@ -34,7 +36,7 @@ void acquiresAllLocksInOrder() throws Exception { var lock1 = mock(Lock.class); var lock2 = mock(Lock.class); - new CompositeLock(List.of(lock1, lock2)).acquire(); + new CompositeLock(anyResources(2), List.of(lock1, lock2)).acquire(); var inOrder = inOrder(lock1, lock2); inOrder.verify(lock1).lockInterruptibly(); @@ -47,7 +49,7 @@ void releasesAllLocksInReverseOrder() throws Exception { var lock1 = mock(Lock.class); var lock2 = mock(Lock.class); - new CompositeLock(List.of(lock1, lock2)).acquire().close(); + new CompositeLock(anyResources(2), List.of(lock1, lock2)).acquire().close(); var inOrder = inOrder(lock1, lock2); inOrder.verify(lock2).unlock(); @@ -64,7 +66,7 @@ void releasesLocksInReverseOrderWhenInterruptedDuringAcquire() throws Exception var thread = new Thread(() -> { try { - new CompositeLock(List.of(firstLock, secondLock, unavailableLock)).acquire(); + new CompositeLock(anyResources(3), List.of(firstLock, secondLock, unavailableLock)).acquire(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -90,4 +92,10 @@ private Lock mockLock(String name, Executable lockAction) throws InterruptedExce return lock; } + private List anyResources(int n) { + return IntStream.range(0, n) // + .mapToObj(j -> new ExclusiveResource("key" + j, LockMode.READ)) // + .toList(); + } + } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java index dde62fa84c50..9a55301ab6af 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/DefaultParallelExecutionConfigurationStrategyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinDeadLockTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinDeadLockTests.java new file mode 100644 index 000000000000..5663e4e0be8c --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinDeadLockTests.java @@ -0,0 +1,179 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; +import static org.junit.jupiter.api.parallel.Resources.SYSTEM_PROPERTIES; + +import java.time.LocalTime; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.engine.Constants; +import org.junit.platform.engine.discovery.ClassSelector; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.testkit.engine.EngineTestKit; + +// https://github.com/junit-team/junit5/issues/3945 +@Timeout(10) +public class ForkJoinDeadLockTests { + + @Test + void forkJoinExecutionDoesNotLeadToDeadLock() { + run(NonIsolatedTestCase.class, IsolatedTestCase.class, Isolated2TestCase.class); + } + + @Test + void nestedResourceLocksShouldStillWork() { + run(SharedResourceTestCase.class); + } + + @Test + void multiLevelLocks() { + run(ClassLevelTestCase.class); + } + + private static void run(Class... classes) { + EngineTestKit.engine("junit-jupiter") // + .selectors(Arrays.stream(classes).map(DiscoverySelectors::selectClass).toArray(ClassSelector[]::new)) // + .configurationParameter(Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, "true") // + .configurationParameter(Constants.DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent") // + .configurationParameter(Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME, "concurrent") // + .configurationParameter(Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") // + .configurationParameter(Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, "3") // + .configurationParameter(Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, "3") // + .configurationParameter(Constants.PARALLEL_CONFIG_FIXED_SATURATE_PROPERTY_NAME, "false") // + .execute(); + } + + @ExtendWith(StartFinishLogger.class) + static class BaseTestCase { + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @Execution(CONCURRENT) + public static class NonIsolatedTestCase extends BaseTestCase { + + public static CountDownLatch otherThreadRunning = new CountDownLatch(1); + public static CountDownLatch sameThreadFinishing = new CountDownLatch(1); + + @Test + @Execution(CONCURRENT) + void otherThread() throws Exception { + otherThreadRunning.countDown(); + sameThreadFinishing.await(); + Thread.sleep(100); + } + + @Test + @Execution(SAME_THREAD) + void sameThread() throws Exception { + otherThreadRunning.await(); + sameThreadFinishing.countDown(); + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @Isolated + public static class IsolatedTestCase extends BaseTestCase { + + @Test + void test() throws Exception { + Thread.sleep(100); + } + } + + static class Isolated2TestCase extends IsolatedTestCase { + } + + @SuppressWarnings("JUnitMalformedDeclaration") + public static class SharedResourceTestCase { + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) + void customPropertyIsNotSetByDefault() { + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + void canSetCustomPropertyToApple() { + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + void canSetCustomPropertyToBanana() { + } + } + + @SuppressWarnings("JUnitMalformedDeclaration") + @ResourceLock(value = "foo", mode = READ_WRITE) + public static class ClassLevelTestCase { + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ) + void customPropertyIsNotSetByDefault() { + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + void canSetCustomPropertyToApple() { + } + + @Test + @ResourceLock(value = SYSTEM_PROPERTIES, mode = READ_WRITE) + void canSetCustomPropertyToBanana() { + } + } + + static class StartFinishLogger + implements BeforeTestExecutionCallback, AfterTestExecutionCallback, BeforeAllCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext context) { + log("starting class " + context.getTestClass().orElseThrow().getSimpleName()); + } + + @Override + public void beforeTestExecution(ExtensionContext context) { + log("starting method " + context.getTestClass().orElseThrow().getSimpleName() + "." + + context.getTestMethod().orElseThrow().getName()); + } + + @Override + public void afterTestExecution(ExtensionContext context) { + log("finishing method " + context.getTestClass().orElseThrow().getSimpleName() + "." + + context.getTestMethod().orElseThrow().getName()); + } + + @Override + public void afterAll(ExtensionContext context) { + log("finishing class " + context.getTestClass().orElseThrow().getSimpleName()); + } + } + + private static void log(String message) { + System.out.println("[" + LocalTime.now() + "] " + Thread.currentThread().getName() + " - " + message); + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java index b9544248f077..59dfbba0f789 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ForkJoinPoolHierarchicalTestExecutorServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,35 +10,354 @@ package org.junit.platform.engine.support.hierarchical; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; +import static org.junit.platform.engine.support.hierarchical.Node.ExecutionMode.CONCURRENT; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.platform.commons.JUnitException; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; +import org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService.TaskEventListener; +import org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutorService.TestTask; +import org.junit.platform.engine.support.hierarchical.Node.ExecutionMode; -@ExtendWith(MockitoExtension.class) +@Timeout(5) class ForkJoinPoolHierarchicalTestExecutorServiceTests { - @Mock - ParallelExecutionConfiguration configuration; + DummyTaskFactory taskFactory = new DummyTaskFactory(); + LockManager lockManager = new LockManager(); @Test void exceptionsFromInvalidConfigurationAreNotSwallowed() { - when(configuration.getParallelism()).thenReturn(2); - when(configuration.getMaxPoolSize()).thenReturn(1); // invalid, should be > parallelism - when(configuration.getCorePoolSize()).thenReturn(1); - when(configuration.getMinimumRunnable()).thenReturn(1); - when(configuration.getSaturatePredicate()).thenReturn(__ -> true); - when(configuration.getKeepAliveSeconds()).thenReturn(0); - - JUnitException exception = assertThrows(JUnitException.class, - () -> new ForkJoinPoolHierarchicalTestExecutorService(configuration)); + var configuration = new DefaultParallelExecutionConfiguration(2, 1, 1, 1, 0, __ -> true); + + JUnitException exception = assertThrows(JUnitException.class, () -> { + try (var pool = new ForkJoinPoolHierarchicalTestExecutorService(configuration)) { + assertNotNull(pool, "we won't get here"); + } + }); + assertThat(exception).hasMessage("Failed to create ForkJoinPool"); assertThat(exception).rootCause().isInstanceOf(IllegalArgumentException.class); } + static List incompatibleLockCombinations() { + return List.of(// + arguments(// + Set.of(GLOBAL_READ), // + Set.of(GLOBAL_READ_WRITE) // + ), // + arguments(// + Set.of(new ExclusiveResource("a", LockMode.READ)), // + Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)) // + ), // + arguments(// + Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)), // + Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)) // + ), // + arguments(// + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ_WRITE)), // + Set.of(GLOBAL_READ, new ExclusiveResource("b", LockMode.READ_WRITE)) // + ), // + arguments(// + Set.of(new ExclusiveResource("b", LockMode.READ)), // + Set.of(new ExclusiveResource("a", LockMode.READ)) // + ), // + arguments(// + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ_WRITE)), // + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)) // + ), // + arguments(// + Set.of(GLOBAL_READ_WRITE), // + Set.of(GLOBAL_READ) // + ), // + arguments(// + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ), + new ExclusiveResource("b", LockMode.READ), new ExclusiveResource("d", LockMode.READ)), + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ), + new ExclusiveResource("c", LockMode.READ)) // + )// + ); + } + + @ParameterizedTest + @MethodSource("incompatibleLockCombinations") + void defersTasksWithIncompatibleLocks(Set initialResources, + Set incompatibleResources) throws Throwable { + + var initialLock = lockManager.getLockForResources(initialResources); + var incompatibleLock = lockManager.getLockForResources(incompatibleResources); + + var deferred = new CountDownLatch(1); + var deferredTask = new AtomicReference(); + + TaskEventListener taskEventListener = testTask -> { + deferredTask.set(testTask); + deferred.countDown(); + }; + + var incompatibleTask = taskFactory.create("incompatibleTask", incompatibleLock); + + var tasks = runWithAttemptedWorkStealing(taskEventListener, incompatibleTask, initialLock, + () -> await(deferred, "Interrupted while waiting for task to be deferred")); + + assertEquals(incompatibleTask, deferredTask.get()); + assertEquals(tasks.get("nestedTask").threadName, tasks.get("leafTaskB").threadName); + assertNotEquals(tasks.get("leafTaskA").threadName, tasks.get("leafTaskB").threadName); + } + + static List compatibleLockCombinations() { + return List.of(// + arguments(// + Set.of(GLOBAL_READ), // + Set.of(new ExclusiveResource("a", LockMode.READ)) // + ), // + arguments(// + Set.of(GLOBAL_READ), // + Set.of(new ExclusiveResource("a", LockMode.READ_WRITE)) // + ), // + arguments(// + Set.of(GLOBAL_READ), // + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ_WRITE)) // + ), // + arguments(// + Set.of(GLOBAL_READ), // + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)) // + ), // + arguments(// + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)), // + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ), + new ExclusiveResource("b", LockMode.READ), new ExclusiveResource("c", LockMode.READ)) // + ), // + arguments(// + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)), // + Set.of(GLOBAL_READ, new ExclusiveResource("b", LockMode.READ)) // + ), // + arguments(// + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ)), // + Set.of(new ExclusiveResource("a", LockMode.READ), new ExclusiveResource("b", LockMode.READ), + new ExclusiveResource("c", LockMode.READ)) // + )// + ); + } + + @ParameterizedTest + @MethodSource("compatibleLockCombinations") + void canWorkStealTaskWithCompatibleLocks(Set initialResources, + Set compatibleResources) throws Throwable { + + var initialLock = lockManager.getLockForResources(initialResources); + var compatibleLock = lockManager.getLockForResources(compatibleResources); + + var deferredTask = new AtomicReference(); + + var workStolen = new CountDownLatch(1); + var compatibleTask = taskFactory.create("compatibleTask", compatibleLock, workStolen::countDown); + + var tasks = runWithAttemptedWorkStealing(deferredTask::set, compatibleTask, initialLock, + () -> await(workStolen, "Interrupted while waiting for work to be stolen")); + + assertNull(deferredTask.get()); + assertEquals(tasks.get("nestedTask").threadName, tasks.get("leafTaskB").threadName); + assertNotEquals(tasks.get("leafTaskA").threadName, tasks.get("leafTaskB").threadName); + } + + @Test + void defersTasksWithIncompatibleLocksOnMultipleLevels() throws Throwable { + + var initialLock = lockManager.getLockForResources( + Set.of(GLOBAL_READ, new ExclusiveResource("a", LockMode.READ))); + var incompatibleLock1 = lockManager.getLockForResource(new ExclusiveResource("a", LockMode.READ_WRITE)); + var compatibleLock1 = lockManager.getLockForResource(new ExclusiveResource("b", LockMode.READ)); + var incompatibleLock2 = lockManager.getLockForResource(new ExclusiveResource("b", LockMode.READ_WRITE)); + + var deferred = new ConcurrentHashMap(); + var deferredTasks = new CopyOnWriteArrayList(); + TaskEventListener taskEventListener = testTask -> { + deferredTasks.add(testTask); + deferred.get(testTask).countDown(); + }; + + var incompatibleTask1 = taskFactory.create("incompatibleTask1", incompatibleLock1); + deferred.put(incompatibleTask1, new CountDownLatch(1)); + + var incompatibleTask2 = taskFactory.create("incompatibleTask2", incompatibleLock2); + deferred.put(incompatibleTask2, new CountDownLatch(1)); + + var configuration = new DefaultParallelExecutionConfiguration(2, 2, 2, 2, 1, __1 -> true); + + withForkJoinPoolHierarchicalTestExecutorService(configuration, taskEventListener, service -> { + + var nestedTask2 = createNestedTaskWithTwoConcurrentLeafTasks(service, "2", compatibleLock1, + List.of(incompatibleTask2), // + () -> await(deferred.get(incompatibleTask2), incompatibleTask2.identifier + " to be deferred")); + + var nestedTask1 = createNestedTaskWithTwoConcurrentLeafTasks(service, "1", initialLock, + List.of(incompatibleTask1, nestedTask2), // + () -> { + await(deferred.get(incompatibleTask1), incompatibleTask1.identifier + " to be deferred"); + await(nestedTask2.started, nestedTask2.identifier + " to be started"); + }); + + service.submit(nestedTask1).get(); + }); + + assertThat(deferredTasks) // + .startsWith(incompatibleTask1, incompatibleTask2) // + .containsOnly(incompatibleTask1, incompatibleTask2) // incompatibleTask1 may be deferred multiple times + .containsOnlyOnce(incompatibleTask2); + assertThat(taskFactory.tasks) // + .hasSize(3 + 3 + 2) // + .values().extracting(it -> it.completion.isDone()).containsOnly(true); + assertThat(taskFactory.tasks) // + .values().extracting(it -> it.completion.isCompletedExceptionally()).containsOnly(false); + } + + private Map runWithAttemptedWorkStealing(TaskEventListener taskEventListener, + DummyTestTask taskToBeStolen, ResourceLock initialLock, Runnable waitAction) throws Throwable { + + var configuration = new DefaultParallelExecutionConfiguration(2, 2, 2, 2, 1, __ -> true); + + withForkJoinPoolHierarchicalTestExecutorService(configuration, taskEventListener, service -> { + + var nestedTask = createNestedTaskWithTwoConcurrentLeafTasks(service, "", initialLock, + List.of(taskToBeStolen), waitAction); + + service.submit(nestedTask).get(); + }); + + return taskFactory.tasks; + } + + private DummyTestTask createNestedTaskWithTwoConcurrentLeafTasks( + ForkJoinPoolHierarchicalTestExecutorService service, String identifierSuffix, ResourceLock parentLock, + List tasksToFork, Runnable waitAction) { + + return taskFactory.create("nestedTask" + identifierSuffix, parentLock, () -> { + + var bothLeafTasksAreRunning = new CountDownLatch(2); + + var leafTaskA = taskFactory.create("leafTaskA" + identifierSuffix, NopLock.INSTANCE, () -> { + tasksToFork.forEach(task -> service.new ExclusiveTask(task).fork()); + bothLeafTasksAreRunning.countDown(); + bothLeafTasksAreRunning.await(); + waitAction.run(); + }); + + var leafTaskB = taskFactory.create("leafTaskB" + identifierSuffix, NopLock.INSTANCE, () -> { + bothLeafTasksAreRunning.countDown(); + bothLeafTasksAreRunning.await(); + }); + + service.invokeAll(List.of(leafTaskA, leafTaskB)); + }); + } + + private static void await(CountDownLatch latch, String message) { + try { + latch.await(); + } + catch (InterruptedException e) { + System.out.println("Interrupted while waiting for " + message); + } + } + + private void withForkJoinPoolHierarchicalTestExecutorService(ParallelExecutionConfiguration configuration, + TaskEventListener taskEventListener, ThrowingConsumer action) + throws Throwable { + try (var service = new ForkJoinPoolHierarchicalTestExecutorService(configuration, taskEventListener)) { + + action.accept(service); + + service.forkJoinPool.shutdown(); + assertTrue(service.forkJoinPool.awaitTermination(5, SECONDS), "Pool did not terminate within timeout"); + } + } + + static final class DummyTestTask implements TestTask { + + private final String identifier; + private final ResourceLock resourceLock; + private final Executable action; + + private volatile String threadName; + private final CountDownLatch started = new CountDownLatch(1); + private final CompletableFuture completion = new CompletableFuture<>(); + + DummyTestTask(String identifier, ResourceLock resourceLock, Executable action) { + this.identifier = identifier; + this.resourceLock = resourceLock; + this.action = action; + } + + @Override + public ExecutionMode getExecutionMode() { + return CONCURRENT; + } + + @Override + public ResourceLock getResourceLock() { + return resourceLock; + } + + @Override + public void execute() { + threadName = Thread.currentThread().getName(); + started.countDown(); + try { + action.execute(); + completion.complete(null); + } + catch (Throwable e) { + completion.completeExceptionally(e); + throw new RuntimeException("Action " + identifier + " failed", e); + } + } + + @Override + public String toString() { + return identifier; + } + } + + static final class DummyTaskFactory { + + final Map tasks = new HashMap<>(); + + DummyTestTask create(String identifier, ResourceLock resourceLock) { + return create(identifier, resourceLock, () -> { + }); + } + + DummyTestTask create(String identifier, ResourceLock resourceLock, Executable action) { + DummyTestTask task = new DummyTestTask(identifier, resourceLock, action); + tasks.put(task.identifier, task); + return task; + } + } } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java index 11d1842ce4a5..09c83e273e60 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/HierarchicalTestExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -18,6 +18,7 @@ import static org.junit.platform.engine.TestExecutionResult.Status.FAILED; import static org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL; import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; @@ -37,6 +38,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.function.ThrowingConsumer; +import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.EngineExecutionListener; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -77,7 +79,8 @@ void init() { private HierarchicalTestExecutor createExecutor( HierarchicalTestExecutorService executorService) { - var request = new ExecutionRequest(root, listener, null); + var request = ExecutionRequest.create(root, listener, mock(ConfigurationParameters.class), + dummyOutputDirectoryProvider()); return new HierarchicalTestExecutor<>(request, rootContext, executorService, OpenTest4JAwareThrowableCollector::new); } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java index 5fca505147f1..ef008b03d221 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/LockManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -32,7 +32,7 @@ */ class LockManagerTests { - private LockManager lockManager = new LockManager(); + private final LockManager lockManager = new LockManager(); @Test void returnsNopLockWithoutExclusiveResources() { @@ -50,7 +50,7 @@ void returnsSingleLockForSingleExclusiveResource() { var locks = getLocks(resources, SingleLock.class); assertThat(locks).hasSize(1); - assertThat(locks.get(0)).isInstanceOf(ReadLock.class); + assertThat(locks.getFirst()).isInstanceOf(ReadLock.class); } @Test @@ -75,7 +75,7 @@ void reusesSameLockForExclusiveResourceWithSameKey() { assertThat(locks1).hasSize(1); assertThat(locks2).hasSize(1); - assertThat(locks1.get(0)).isSameAs(locks2.get(0)); + assertThat(locks1.getFirst()).isSameAs(locks2.getFirst()); } @Test @@ -111,8 +111,26 @@ void globalLockComesFirst(LockMode globalLockMode) { assertThat(locks.get(3)).isEqualTo(getSingleLock("foo", READ_WRITE)); } - private Lock getSingleLock(String globalResourceLockKey, LockMode read) { - return getLocks(Set.of(new ExclusiveResource(globalResourceLockKey, read)), SingleLock.class).get(0); + @Test + void usesSingleInstanceForGlobalReadLock() { + var lock = lockManager.getLockForResources(List.of(ExclusiveResource.GLOBAL_READ)); + + assertThat(lock) // + .isInstanceOf(SingleLock.class) // + .isSameAs(lockManager.getLockForResource(ExclusiveResource.GLOBAL_READ)); + } + + @Test + void usesSingleInstanceForGlobalReadWriteLock() { + var lock = lockManager.getLockForResources(List.of(ExclusiveResource.GLOBAL_READ_WRITE)); + + assertThat(lock) // + .isInstanceOf(SingleLock.class) // + .isSameAs(lockManager.getLockForResource(ExclusiveResource.GLOBAL_READ_WRITE)); + } + + private Lock getSingleLock(String key, LockMode lockMode) { + return getLocks(Set.of(new ExclusiveResource(key, lockMode)), SingleLock.class).getFirst(); } private List getLocks(Collection resources, Class type) { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java index ad97557e5fb6..e8daa4b230a5 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/MemoryLeakTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java index 531c61b66cf3..4857212814cb 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/NodeTreeWalkerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.ResourceAccessMode; import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Resources; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; @@ -161,12 +162,12 @@ void coarsensGlobalLockToEngineDescriptorChild() { var nestedTestClassDescriptor = getOnlyElement(testClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(nestedTestClassDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ))); + .isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(nestedTestClassDescriptor)).contains(SAME_THREAD); var testMethodDescriptor = getOnlyElement(nestedTestClassDescriptor.getChildren()); assertThat(advisor.getResourceLock(testMethodDescriptor)).extracting(allLocks()) // - .isEqualTo(List.of(getLock(GLOBAL_READ_WRITE))); + .isEqualTo(List.of()); assertThat(advisor.getForcedExecutionMode(testMethodDescriptor)).contains(SAME_THREAD); } @@ -191,6 +192,7 @@ private TestDescriptor discover(Class testClass) { return new JupiterTestEngine().discover(discoveryRequest, UniqueId.forEngine("junit-jupiter")); } + @SuppressWarnings("JUnitMalformedDeclaration") @ResourceLock("a") static class TestCaseWithResourceLock { @Test @@ -199,6 +201,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCaseWithoutResourceLock { @Test @ResourceLock("a") @@ -215,6 +218,7 @@ void test() { } } + @ResourceLock(Resources.SYSTEM_PROPERTIES) static class TestCaseWithGlobalLockRequiringChild { @Nested class NestedTestCaseWithResourceLock { @@ -225,6 +229,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ResourceLock("a") static class TestCaseWithResourceWriteLockOnClass { @Test @@ -232,6 +237,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ResourceLock(value = "a", mode = ResourceAccessMode.READ) static class TestCaseWithResourceReadLockOnClass { @Test @@ -239,6 +245,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ResourceLock(value = "a", mode = ResourceAccessMode.READ) static class TestCaseWithResourceReadLockOnClassAndWriteClockOnTestCase { @Test @@ -247,6 +254,7 @@ void test() { } } + @SuppressWarnings("JUnitMalformedDeclaration") @ResourceLock(value = "a", mode = ResourceAccessMode.READ) static class TestCaseWithResourceReadLockOnClassAndReadClockOnTestCase { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java index dbd5d519be63..2775585bb9da 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ParallelExecutionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,19 +11,21 @@ package org.junit.platform.engine.support.hierarchical; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.DynamicTest.dynamicTest; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +import static org.junit.jupiter.api.parallel.ResourceAccessMode.READ_WRITE; import static org.junit.jupiter.engine.Constants.DEFAULT_CLASSES_EXECUTION_MODE_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.DEFAULT_PARALLEL_EXECUTION_MODE; +import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME; import static org.junit.jupiter.engine.Constants.PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME; -import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_KEY; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.event; import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; @@ -64,21 +66,27 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.discovery.ClassSelector; import org.junit.platform.engine.discovery.DiscoverySelectors; import org.junit.platform.engine.reporting.ReportEntry; +import org.junit.platform.engine.support.descriptor.MethodSource; import org.junit.platform.testkit.engine.EngineExecutionResults; import org.junit.platform.testkit.engine.EngineTestKit; import org.junit.platform.testkit.engine.Event; +import org.junit.platform.testkit.engine.Events; /** * @since 1.3 */ +@SuppressWarnings({ "JUnitMalformedDeclaration", "NewClassNamingConvention" }) class ParallelExecutionIntegrationTests { @Test void successfulParallelTest(TestReporter reporter) { - var events = executeConcurrently(3, SuccessfulParallelTestCase.class); + var events = executeConcurrentlySuccessfully(3, SuccessfulParallelTestCase.class).list(); var startedTimestamps = getTimestampsFor(events, event(test(), started())); var finishedTimestamps = getTimestampsFor(events, event(test(), finishedSuccessfully())); @@ -94,13 +102,13 @@ void successfulParallelTest(TestReporter reporter) { @Test void failingTestWithoutLock() { - var events = executeConcurrently(3, FailingWithoutLockTestCase.class); + var events = executeConcurrently(3, FailingWithoutLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).hasSize(2); } @Test void successfulTestWithMethodLock() { - var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); + var events = executeConcurrentlySuccessfully(3, SuccessfulWithMethodLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); @@ -108,7 +116,7 @@ void successfulTestWithMethodLock() { @Test void successfulTestWithClassLock() { - var events = executeConcurrently(3, SuccessfulWithClassLockTestCase.class); + var events = executeConcurrentlySuccessfully(3, SuccessfulWithClassLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); @@ -116,7 +124,7 @@ void successfulTestWithClassLock() { @Test void testCaseWithFactory() { - var events = executeConcurrently(3, TestCaseWithTestFactory.class); + var events = executeConcurrentlySuccessfully(3, TestCaseWithTestFactory.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); @@ -129,7 +137,7 @@ void customContextClassLoader() { var smilingLoader = new URLClassLoader("(-:", new URL[0], ClassLoader.getSystemClassLoader()); currentThread.setContextClassLoader(smilingLoader); try { - var events = executeConcurrently(3, SuccessfulWithMethodLockTestCase.class); + var events = executeConcurrentlySuccessfully(3, SuccessfulWithMethodLockTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(3); assertThat(ThreadReporter.getThreadNames(events)).hasSize(3); @@ -142,7 +150,8 @@ void customContextClassLoader() { @RepeatedTest(10) void mixingClassAndMethodLevelLocks() { - var events = executeConcurrently(4, TestCaseWithSortedLocks.class, TestCaseWithUnsortedLocks.class); + var events = executeConcurrentlySuccessfully(4, TestCaseWithSortedLocks.class, + TestCaseWithUnsortedLocks.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); assertThat(ThreadReporter.getThreadNames(events).count()).isLessThanOrEqualTo(2); @@ -150,7 +159,7 @@ void mixingClassAndMethodLevelLocks() { @RepeatedTest(10) void locksOnNestedTests() { - var events = executeConcurrently(3, TestCaseWithNestedLocks.class); + var events = executeConcurrentlySuccessfully(3, TestCaseWithNestedLocks.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(6); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); @@ -158,7 +167,7 @@ void locksOnNestedTests() { @Test void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() { - var events = executeConcurrently(3, ConcurrentDynamicTestCase.class); + var events = executeConcurrentlySuccessfully(3, ConcurrentDynamicTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(1); var timestampedEvents = ConcurrentDynamicTestCase.events; @@ -171,14 +180,14 @@ void afterHooksAreCalledAfterConcurrentDynamicTestsAreFinished() { */ @Test void threadInterruptedByUserCode() { - var events = executeConcurrently(3, InterruptedThreadTestCase.class); + var events = executeConcurrentlySuccessfully(3, InterruptedThreadTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(4); } @Test void executesTestTemplatesWithResourceLocksInSameThread() { - var events = executeConcurrently(2, ConcurrentTemplateTestCase.class); + var events = executeConcurrentlySuccessfully(2, ConcurrentTemplateTestCase.class).list(); assertThat(events.stream().filter(event(test(), finishedSuccessfully())::matches)).hasSize(10); assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); @@ -224,30 +233,59 @@ void executesMethodsInParallelIfEnabledViaConfigurationParameter() { @Test void canRunTestsIsolatedFromEachOther() { - var events = executeConcurrently(2, IsolatedTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + executeConcurrentlySuccessfully(2, IsolatedTestCase.class); } @Test void canRunTestsIsolatedFromEachOtherWithNestedCases() { - var events = executeConcurrently(4, NestedIsolatedTestCase.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + executeConcurrentlySuccessfully(4, NestedIsolatedTestCase.class); } @Test void canRunTestsIsolatedFromEachOtherAcrossClasses() { - var events = executeConcurrently(4, IndependentClasses.A.class, IndependentClasses.B.class); - - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + executeConcurrentlySuccessfully(4, IndependentClasses.A.class, IndependentClasses.B.class); } @RepeatedTest(10) void canRunTestsIsolatedFromEachOtherAcrossClassesWithOtherResourceLocks() { - var events = executeConcurrently(4, IndependentClasses.B.class, IndependentClasses.C.class); + executeConcurrentlySuccessfully(4, IndependentClasses.B.class, IndependentClasses.C.class); + } + + @Test + void runsIsolatedTestsLastToMaximizeParallelism() { + var configParams = Map.of( // + DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent", // + PARALLEL_CONFIG_FIXED_MAX_POOL_SIZE_PROPERTY_NAME, "3" // + ); + Class[] testClasses = { IsolatedTestCase.class, SuccessfulParallelTestCase.class }; + var events = executeWithFixedParallelism(3, configParams, testClasses) // + .allEvents() // + .assertStatistics(it -> it.failed(0)); + + List parallelTestMethodEvents = events.reportingEntryPublished() // + .filter(e -> e.getTestDescriptor().getSource() // + .filter(it -> // + it instanceof MethodSource + && SuccessfulParallelTestCase.class.equals(((MethodSource) it).getJavaClass()) // + ).isPresent() // + ) // + .toList(); + assertThat(ThreadReporter.getThreadNames(parallelTestMethodEvents)).hasSize(3); + + var parallelClassFinish = getOnlyElement(getTimestampsFor(events.list(), + event(container(SuccessfulParallelTestCase.class), finishedSuccessfully()))); + var isolatedClassStart = getOnlyElement( + getTimestampsFor(events.list(), event(container(IsolatedTestCase.class), started()))); + assertThat(isolatedClassStart).isAfterOrEqualTo(parallelClassFinish); + } + + @ParameterizedTest + @ValueSource(classes = { IsolatedMethodFirstTestCase.class, IsolatedMethodLastTestCase.class, + IsolatedNestedMethodFirstTestCase.class, IsolatedNestedMethodLastTestCase.class }) + void canRunTestsIsolatedFromEachOtherWhenDeclaredOnMethodLevel(Class testClass) { + List events = executeConcurrentlySuccessfully(1, testClass).list(); - assertThat(events.stream().filter(event(test(), finishedWithFailure())::matches)).isEmpty(); + assertThat(ThreadReporter.getThreadNames(events)).hasSize(1); } @Isolated("testing") @@ -322,6 +360,122 @@ void b() throws Exception { } } + @ExtendWith(ThreadReporter.class) + static class IsolatedMethodFirstTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(2); + } + + @Test + @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated + void test1() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + @ResourceLock(value = "b", mode = READ_WRITE) + void test2() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + @ExtendWith(ThreadReporter.class) + static class IsolatedMethodLastTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(2); + } + + @Test + @ResourceLock(value = "b", mode = READ_WRITE) + void test1() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + + @Test + @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated + void test2() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + @ExtendWith(ThreadReporter.class) + static class IsolatedNestedMethodFirstTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(2); + } + + @Nested + class Test1 { + + @Test + @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated + void test1() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + @Nested + class Test2 { + + @Test + @ResourceLock(value = "b", mode = READ_WRITE) + void test2() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + } + + @ExtendWith(ThreadReporter.class) + static class IsolatedNestedMethodLastTestCase { + + static AtomicInteger sharedResource; + static CountDownLatch countDownLatch; + + @BeforeAll + static void initialize() { + sharedResource = new AtomicInteger(); + countDownLatch = new CountDownLatch(2); + } + + @Nested + class Test1 { + + @Test + @ResourceLock(value = "b", mode = READ_WRITE) + void test1() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + + @Nested + class Test2 { + + @Test + @ResourceLock(value = GLOBAL_KEY, mode = READ_WRITE) // effectively @Isolated + void test2() throws InterruptedException { + incrementBlockAndCheck(sharedResource, countDownLatch); + } + } + } + static class IndependentClasses { static AtomicInteger sharedResource = new AtomicInteger(); static CountDownLatch countDownLatch = new CountDownLatch(4); @@ -367,7 +521,7 @@ void b() throws Exception { private List getEventsOfChildren(EngineExecutionResults results, TestDescriptor container) { return results.testEvents().filter( - event -> event.getTestDescriptor().getParent().orElseThrow().equals(container)).collect(toList()); + event -> event.getTestDescriptor().getParent().orElseThrow().equals(container)).toList(); } private TestDescriptor findFirstTestDescriptor(EngineExecutionResults results, Condition condition) { @@ -379,32 +533,45 @@ private List getTimestampsFor(List events, Condition cond return events.stream() .filter(condition::matches) .map(Event::getTimestamp) - .collect(toList()); + .toList(); // @formatter:on } - private List executeConcurrently(int parallelism, Class... testClasses) { - return executeWithFixedParallelism(parallelism, Map.of(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent"), - testClasses).allEvents().list(); + private Events executeConcurrentlySuccessfully(int parallelism, Class... testClasses) { + var events = executeConcurrently(parallelism, testClasses); + try { + return events.assertStatistics(it -> it.failed(0)); + } + catch (AssertionError error) { + events.debug(); + throw error; + } + } + + private Events executeConcurrently(int parallelism, Class... testClasses) { + Map configParams = Map.of(DEFAULT_PARALLEL_EXECUTION_MODE, "concurrent"); + return executeWithFixedParallelism(parallelism, configParams, testClasses) // + .allEvents(); } private EngineExecutionResults executeWithFixedParallelism(int parallelism, Map configParams, Class... testClasses) { - // @formatter:off - var discoveryRequest = request() - .selectors(Arrays.stream(testClasses).map(DiscoverySelectors::selectClass).collect(toList())) - .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, String.valueOf(true)) - .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") - .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, String.valueOf(parallelism)) - .configurationParameters(configParams) - .build(); - // @formatter:on - return EngineTestKit.execute("junit-jupiter", discoveryRequest); + var classSelectors = Arrays.stream(testClasses) // + .map(DiscoverySelectors::selectClass) // + .toArray(ClassSelector[]::new); + return EngineTestKit.engine("junit-jupiter") // + .selectors(classSelectors) // + .configurationParameter(PARALLEL_EXECUTION_ENABLED_PROPERTY_NAME, String.valueOf(true)) // + .configurationParameter(PARALLEL_CONFIG_STRATEGY_PROPERTY_NAME, "fixed") // + .configurationParameter(PARALLEL_CONFIG_FIXED_PARALLELISM_PROPERTY_NAME, String.valueOf(parallelism)) // + .configurationParameters(configParams) // + .execute(); } // ------------------------------------------------------------------------- @ExtendWith(ThreadReporter.class) + @Execution(SAME_THREAD) static class SuccessfulParallelTestCase { static AtomicInteger sharedResource; @@ -417,16 +584,19 @@ static void initialize() { } @Test + @Execution(CONCURRENT) void firstTest() throws Exception { incrementAndBlock(sharedResource, countDownLatch); } @Test + @Execution(CONCURRENT) void secondTest() throws Exception { incrementAndBlock(sharedResource, countDownLatch); } @Test + @Execution(CONCURRENT) void thirdTest() throws Exception { incrementAndBlock(sharedResource, countDownLatch); } @@ -782,23 +952,25 @@ private static void incrementBlockAndCheck(AtomicInteger sharedResource, CountDo assertEquals(value, sharedResource.get()); } + @SuppressWarnings("ResultOfMethodCallIgnored") private static int incrementAndBlock(AtomicInteger sharedResource, CountDownLatch countDownLatch) throws InterruptedException { var value = sharedResource.incrementAndGet(); countDownLatch.countDown(); - countDownLatch.await(estimateSimulatedTestDurationInMiliseconds(), MILLISECONDS); + countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); return value; } + @SuppressWarnings("ResultOfMethodCallIgnored") private static void storeAndBlockAndCheck(AtomicInteger sharedResource, CountDownLatch countDownLatch) throws InterruptedException { var value = sharedResource.get(); countDownLatch.countDown(); - countDownLatch.await(estimateSimulatedTestDurationInMiliseconds(), MILLISECONDS); + countDownLatch.await(estimateSimulatedTestDurationInMilliseconds(), MILLISECONDS); assertEquals(value, sharedResource.get()); } - /** + /* * To simulate tests running in parallel tests will modify a shared * resource, simulate work by waiting, then check if the shared resource was * not modified by any other thread. @@ -806,10 +978,10 @@ private static void storeAndBlockAndCheck(AtomicInteger sharedResource, CountDow * Depending on system performance the simulation of work needs to be longer * on slower systems to ensure tests can run in parallel. * - * Currently CI is known to be slow. + * Currently, CI is known to be slow. */ - private static long estimateSimulatedTestDurationInMiliseconds() { - var runningInCi = Boolean.valueOf(System.getenv("CI")); + private static long estimateSimulatedTestDurationInMilliseconds() { + var runningInCi = Boolean.parseBoolean(System.getenv("CI")); return runningInCi ? 1000 : 100; } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java index 1660eb08f1b6..8325fb7b30a9 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockTests.java new file mode 100644 index 000000000000..40fbdf0be196 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ResourceLockTests.java @@ -0,0 +1,150 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.engine.support.hierarchical; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ; +import static org.junit.platform.engine.support.hierarchical.ExclusiveResource.GLOBAL_READ_WRITE; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.junit.jupiter.api.Test; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; + +class ResourceLockTests { + + @Test + void nopLocks() { + assertCompatible(nopLock(), nopLock()); + assertCompatible(nopLock(), singleLock(anyReadOnlyResource())); + assertCompatible(nopLock(), compositeLock(anyReadOnlyResource())); + } + + @Test + void readOnlySingleLocks() { + ExclusiveResource bR = readOnlyResource("b"); + + assertCompatible(singleLock(bR), nopLock()); + assertCompatible(singleLock(bR), singleLock(bR)); + assertIncompatible(singleLock(bR), singleLock(readWriteResource("b")), "read-write conflict"); + assertIncompatible(singleLock(bR), singleLock(readOnlyResource("a")), "lock acquisition order"); + assertCompatible(singleLock(bR), singleLock(readOnlyResource("c"))); + assertIncompatible(singleLock(bR), singleLock(GLOBAL_READ), "lock acquisition order"); + assertIncompatible(singleLock(bR), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); + assertCompatible(singleLock(bR), compositeLock(bR, readOnlyResource("c"))); + assertIncompatible(singleLock(bR), compositeLock(readOnlyResource("a1"), readOnlyResource("a2"), bR), + "lock acquisition order"); + } + + @Test + void readWriteSingleLocks() { + ExclusiveResource bRW = readWriteResource("b"); + + assertCompatible(singleLock(bRW), nopLock()); + assertIncompatible(singleLock(bRW), singleLock(bRW), "isolation guarantees"); + assertIncompatible(singleLock(bRW), compositeLock(bRW), "isolation guarantees"); + assertIncompatible(singleLock(bRW), singleLock(readOnlyResource("a")), "lock acquisition order"); + assertIncompatible(singleLock(bRW), singleLock(readOnlyResource("b")), "isolation guarantees"); + assertIncompatible(singleLock(bRW), singleLock(readOnlyResource("c")), "isolation guarantees"); + assertIncompatible(singleLock(bRW), singleLock(GLOBAL_READ), "lock acquisition order"); + assertIncompatible(singleLock(bRW), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); + assertIncompatible(singleLock(bRW), compositeLock(bRW, readOnlyResource("c")), "isolation guarantees"); + assertIncompatible(singleLock(bRW), compositeLock(readOnlyResource("a1"), readOnlyResource("a2"), bRW), + "lock acquisition order"); + } + + @Test + void globalReadLock() { + assertCompatible(singleLock(GLOBAL_READ), nopLock()); + assertCompatible(singleLock(GLOBAL_READ), singleLock(GLOBAL_READ)); + assertCompatible(singleLock(GLOBAL_READ), singleLock(anyReadOnlyResource())); + assertCompatible(singleLock(GLOBAL_READ), singleLock(anyReadWriteResource())); + assertIncompatible(singleLock(GLOBAL_READ), singleLock(GLOBAL_READ_WRITE), "read-write conflict"); + } + + @Test + void readOnlyCompositeLocks() { + ExclusiveResource bR = readOnlyResource("b"); + + assertCompatible(compositeLock(bR), nopLock()); + assertCompatible(compositeLock(bR), singleLock(bR)); + assertCompatible(compositeLock(bR), compositeLock(bR)); + assertIncompatible(compositeLock(bR), singleLock(GLOBAL_READ), "lock acquisition order"); + assertIncompatible(compositeLock(bR), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); + assertIncompatible(compositeLock(bR), compositeLock(readOnlyResource("a")), "lock acquisition order"); + assertCompatible(compositeLock(bR), compositeLock(readOnlyResource("c"))); + assertIncompatible(compositeLock(bR), compositeLock(readWriteResource("b")), "read-write conflict"); + assertIncompatible(compositeLock(bR), compositeLock(bR, readWriteResource("b")), "read-write conflict"); + } + + @Test + void readWriteCompositeLocks() { + ExclusiveResource bRW = readWriteResource("b"); + + assertCompatible(compositeLock(bRW), nopLock()); + assertIncompatible(compositeLock(bRW), singleLock(bRW), "isolation guarantees"); + assertIncompatible(compositeLock(bRW), compositeLock(bRW), "isolation guarantees"); + assertIncompatible(compositeLock(bRW), singleLock(readOnlyResource("a")), "lock acquisition order"); + assertIncompatible(compositeLock(bRW), singleLock(readOnlyResource("b")), "isolation guarantees"); + assertIncompatible(compositeLock(bRW), singleLock(readOnlyResource("c")), "isolation guarantees"); + assertIncompatible(compositeLock(bRW), singleLock(GLOBAL_READ), "lock acquisition order"); + assertIncompatible(compositeLock(bRW), singleLock(GLOBAL_READ_WRITE), "lock acquisition order"); + assertIncompatible(compositeLock(bRW), compositeLock(readOnlyResource("a")), "lock acquisition order"); + assertIncompatible(compositeLock(bRW), compositeLock(readOnlyResource("b"), readOnlyResource("c")), + "isolation guarantees"); + } + + private static void assertCompatible(ResourceLock first, ResourceLock second) { + assertTrue(first.isCompatible(second), + "Expected locks to be compatible:\n(1) %s\n(2) %s".formatted(first, second)); + } + + private static void assertIncompatible(ResourceLock first, ResourceLock second, String reason) { + assertFalse(first.isCompatible(second), + "Expected locks to be incompatible due to %s:\n(1) %s\n(2) %s".formatted(reason, first, second)); + } + + private static ResourceLock nopLock() { + return NopLock.INSTANCE; + } + + private static SingleLock singleLock(ExclusiveResource resource) { + return new SingleLock(resource, anyLock()); + } + + private static CompositeLock compositeLock(ExclusiveResource... resources) { + return new CompositeLock(List.of(resources), Arrays.stream(resources).map(__ -> anyLock()).toList()); + } + + private static ExclusiveResource anyReadOnlyResource() { + return readOnlyResource("key"); + } + + private static ExclusiveResource anyReadWriteResource() { + return readWriteResource("key"); + } + + private static ExclusiveResource readOnlyResource(String key) { + return new ExclusiveResource(key, LockMode.READ); + } + + private static ExclusiveResource readWriteResource(String key) { + return new ExclusiveResource(key, LockMode.READ_WRITE); + } + + private static Lock anyLock() { + return new ReentrantLock(); + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java index 64b1939cfe5e..c6e82bb1ae37 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SameThreadExecutionIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -53,6 +53,7 @@ private LogRecord firstDebugLogRecord(LogRecordListener listener) throws Asserti // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") @TestMethodOrder(MethodName.class) static class InterruptedThreadTestCase { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java index edf36cf66d4e..d988d151435c 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleLockTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import java.util.concurrent.locks.ReentrantLock; import org.junit.jupiter.api.Test; +import org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode; /** * @since 1.3 @@ -27,7 +28,7 @@ class SingleLockTests { void acquire() throws Exception { var lock = new ReentrantLock(); - new SingleLock(lock).acquire(); + new SingleLock(anyResource(), lock).acquire(); assertTrue(lock.isLocked()); } @@ -37,9 +38,13 @@ void acquire() throws Exception { void release() throws Exception { var lock = new ReentrantLock(); - new SingleLock(lock).acquire().close(); + new SingleLock(anyResource(), lock).acquire().close(); assertFalse(lock.isLocked()); } + private static ExclusiveResource anyResource() { + return new ExclusiveResource("key", LockMode.READ); + } + } diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java index e80890a1d6ef..35b139b12243 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/SingleTestExecutorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java index bf5ca1bffc32..fbf7864e08a9 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/hierarchical/ThrowableCollectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java index 9f18083a924c..77b14f19ca6a 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/store/NamespacedHierarchicalStoreTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -519,28 +519,35 @@ void closeIsIdempotent() throws Throwable { verifyNoMoreInteractions(closeAction); } + /** + * @see #3944 + */ @Test - void rejectsModificationAfterClose() { + void acceptsQueryAfterClose() { + store.put(namespace, key, value); store.close(); assertClosed(); - assertThrows(NamespacedHierarchicalStoreException.class, () -> store.put(namespace, "key1", "value1")); - assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, "key1")); - assertThrows(NamespacedHierarchicalStoreException.class, - () -> store.remove(namespace, "key1", Number.class)); + assertThat(store.get(namespace, key)).isEqualTo(value); + assertThat(store.get(namespace, key, String.class)).isEqualTo(value); + assertThat(store.getOrComputeIfAbsent(namespace, key, k -> "new")).isEqualTo(value); + assertThat(store.getOrComputeIfAbsent(namespace, key, k -> "new", String.class)).isEqualTo(value); } @Test - void rejectsQueryAfterClose() { + void rejectsModificationAfterClose() { store.close(); assertClosed(); - assertThrows(NamespacedHierarchicalStoreException.class, () -> store.get(namespace, "key1")); - assertThrows(NamespacedHierarchicalStoreException.class, () -> store.get(namespace, "key1", Integer.class)); + assertThrows(NamespacedHierarchicalStoreException.class, () -> store.put(namespace, key, value)); + assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key)); + assertThrows(NamespacedHierarchicalStoreException.class, () -> store.remove(namespace, key, int.class)); + + // Since key does not exist, an invocation of getOrComputeIfAbsent(...) will attempt to compute a new value. assertThrows(NamespacedHierarchicalStoreException.class, - () -> store.getOrComputeIfAbsent(namespace, "key1", k -> "value")); + () -> store.getOrComputeIfAbsent(namespace, key, k -> "new")); assertThrows(NamespacedHierarchicalStoreException.class, - () -> store.getOrComputeIfAbsent(namespace, "key1", k -> 1337, Integer.class)); + () -> store.getOrComputeIfAbsent(namespace, key, k -> "new", String.class)); } private void assertNotClosed() { 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 2767ec89746e..5fe5dbe2aeb6 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java index c209c692907b..afda445bc142 100644 --- a/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/jfr/FlightRecordingExecutionListenerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,13 +12,20 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; +import static org.junit.platform.reporting.testutil.FileUtils.findPath; import static org.moditect.jfrunit.ExpectedEvent.event; import static org.moditect.jfrunit.JfrEventsAssert.assertThat; +import java.nio.file.Files; +import java.nio.file.Path; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly; import org.moditect.jfrunit.EnableEvent; @@ -33,15 +40,18 @@ public class FlightRecordingExecutionListenerIntegrationTests { @Test @EnableEvent("org.junit.*") - void reportsEvents() { + void reportsEvents(@TempDir Path tempDir) { var launcher = LauncherFactoryForTestingPurposesOnly.createLauncher(new JupiterTestEngine()); var request = request() // .selectors(selectClass(TestCase.class)) // + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(tempDir)) // .build(); launcher.execute(request, new FlightRecordingExecutionListener()); jfrEvents.awaitEvents(); + var testFile = findPath(tempDir, "glob:**/test.txt"); + assertThat(jfrEvents) // .contains(event("org.junit.TestPlanExecution") // .with("engineNames", "JUnit Jupiter")) // @@ -59,16 +69,20 @@ void reportsEvents() { .contains(event("org.junit.ReportEntry") // .with("key", "message") // .with("value", "Hello JFR!")) // + .contains(event("org.junit.FileEntry") // + .with("path", testFile.toAbsolutePath().toString())) // .contains(event("org.junit.SkippedTest") // .with("displayName", "skipped()") // .with("type", "TEST") // .with("reason", "for demonstration purposes")); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase { @Test void test(TestReporter reporter) { reporter.publishEntry("message", "Hello JFR!"); + reporter.publishFile("test.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "test")); } @Test diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java b/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java index a31de9b54f55..088d35c34862 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/DiscoveryFilterStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java b/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java index 01771f42889b..f340397ab287 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/FilterStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/InterceptedTestEngine.java b/platform-tests/src/test/java/org/junit/platform/launcher/InterceptedTestEngine.java index ce90b6eb1620..253f7434a0ca 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/InterceptedTestEngine.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/InterceptedTestEngine.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/InterceptorInjectedLauncherSessionListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/InterceptorInjectedLauncherSessionListener.java index 606f6cedef5f..982ed805cb04 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/InterceptorInjectedLauncherSessionListener.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/InterceptorInjectedLauncherSessionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java new file mode 100644 index 000000000000..10a3d7931788 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/launcher/MethodFilterTests.java @@ -0,0 +1,172 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.platform.launcher.MethodFilter.excludeMethodNamePatterns; +import static org.junit.platform.launcher.MethodFilter.includeMethodNamePatterns; + +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.util.ReflectionUtils; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; + +/** + * Unit tests for {@link MethodFilter}. + * + * @since 1.12 + */ +class MethodFilterTests { + private static final String CLASS1_TEST1_NAME = "org.junit.platform.launcher.MethodFilterTests$Class1#test1"; + private static final String CLASS1_TEST2_NAME = "org.junit.platform.launcher.MethodFilterTests$Class1#test2"; + private static final String CLASS2_TEST1_NAME = "org.junit.platform.launcher.MethodFilterTests$Class2#test1"; + private static final String CLASS2_TEST2_NAME = "org.junit.platform.launcher.MethodFilterTests$Class2#test2"; + private static final TestDescriptor CLASS1_TEST1 = methodTestDescriptor("class1", Class1.class, "test1"); + private static final TestDescriptor CLASS1_TEST2 = methodTestDescriptor("class1", Class1.class, "test2"); + private static final TestDescriptor CLASS2_TEST1 = methodTestDescriptor("class2", Class2.class, "test1"); + private static final TestDescriptor CLASS2_TEST2 = methodTestDescriptor("class2", Class2.class, "test2"); + + @Test + void includeMethodNamePatternsChecksPreconditions() { + assertThatThrownBy(() -> includeMethodNamePatterns((String[]) null)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> includeMethodNamePatterns(new String[0])) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> includeMethodNamePatterns(new String[] { null })) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not contain null elements"); + } + + @Test + void includeSingleMethodNamePattern() { + var regex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; + var filter = includeMethodNamePatterns(regex); + + assertIncluded(filter.apply(CLASS1_TEST1), + String.format("Method name [%s] matches included pattern: '%s'", CLASS1_TEST1_NAME, regex)); + assertIncluded(filter.apply(CLASS1_TEST2), + String.format("Method name [%s] matches included pattern: '%s'", CLASS1_TEST2_NAME, regex)); + + assertExcluded(filter.apply(CLASS2_TEST1), + String.format("Method name [%s] does not match any included pattern: '%s'", CLASS2_TEST1_NAME, regex)); + assertExcluded(filter.apply(CLASS2_TEST2), + String.format("Method name [%s] does not match any included pattern: '%s'", CLASS2_TEST2_NAME, regex)); + } + + @Test + void includeMultipleMethodNamePatterns() { + var firstRegex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; + var secondRegex = ".+Class.+#test1"; + var filter = includeMethodNamePatterns(firstRegex, secondRegex); + + assertIncluded(filter.apply(CLASS1_TEST1), + String.format("Method name [%s] matches included pattern: '%s'", CLASS1_TEST1_NAME, firstRegex)); + assertIncluded(filter.apply(CLASS1_TEST2), + String.format("Method name [%s] matches included pattern: '%s'", CLASS1_TEST2_NAME, firstRegex)); + assertIncluded(filter.apply(CLASS2_TEST1), + String.format("Method name [%s] matches included pattern: '%s'", CLASS2_TEST1_NAME, secondRegex)); + + assertExcluded(filter.apply(CLASS2_TEST2), + String.format("Method name [%s] does not match any included pattern: '%s' OR '%s'", CLASS2_TEST2_NAME, + firstRegex, secondRegex)); + } + + @Test + void excludeMethodNamePatternsChecksPreconditions() { + assertThatThrownBy(() -> excludeMethodNamePatterns((String[]) null)) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> excludeMethodNamePatterns(new String[0])) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not be null or empty"); + assertThatThrownBy(() -> excludeMethodNamePatterns(new String[] { null })) // + .isInstanceOf(PreconditionViolationException.class) // + .hasMessage("patterns array must not contain null elements"); + } + + @Test + void excludeSingleMethodNamePattern() { + var regex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; + var filter = excludeMethodNamePatterns(regex); + + assertExcluded(filter.apply(CLASS1_TEST1), + String.format("Method name [%s] matches excluded pattern: '%s'", CLASS1_TEST1_NAME, regex)); + assertExcluded(filter.apply(CLASS1_TEST2), + String.format("Method name [%s] matches excluded pattern: '%s'", CLASS1_TEST2_NAME, regex)); + + assertIncluded(filter.apply(CLASS2_TEST1), + String.format("Method name [%s] does not match any excluded pattern: '%s'", CLASS2_TEST1_NAME, regex)); + assertIncluded(filter.apply(CLASS2_TEST2), + String.format("Method name [%s] does not match any excluded pattern: '%s'", CLASS2_TEST2_NAME, regex)); + } + + @Test + void excludeMultipleMethodNamePatterns() { + var firstRegex = "^org\\.junit\\.platform\\.launcher\\.MethodFilterTests\\$Class1#test.*"; + var secondRegex = ".+Class.+#test1"; + var filter = excludeMethodNamePatterns(firstRegex, secondRegex); + + assertExcluded(filter.apply(CLASS1_TEST1), + String.format("Method name [%s] matches excluded pattern: '%s'", CLASS1_TEST1_NAME, firstRegex)); + assertExcluded(filter.apply(CLASS1_TEST2), + String.format("Method name [%s] matches excluded pattern: '%s'", CLASS1_TEST2_NAME, firstRegex)); + assertExcluded(filter.apply(CLASS2_TEST1), + String.format("Method name [%s] matches excluded pattern: '%s'", CLASS2_TEST1_NAME, secondRegex)); + + assertIncluded(filter.apply(CLASS2_TEST2), + String.format("Method name [%s] does not match any excluded pattern: '%s' OR '%s'", CLASS2_TEST2_NAME, + firstRegex, secondRegex)); + } + + private void assertIncluded(FilterResult filterResult, String expectedReason) { + assertTrue(filterResult.included()); + assertThat(filterResult.getReason()).isPresent().contains(expectedReason); + } + + private void assertExcluded(FilterResult filterResult, String excludedPattern) { + assertTrue(filterResult.excluded()); + assertThat(filterResult.getReason()).isPresent().contains(excludedPattern); + } + + private static TestDescriptor methodTestDescriptor(String uniqueId, Class testClass, String methodName) { + var method = ReflectionUtils.findMethod(testClass, methodName, new Class[0]).orElseThrow(); + return new DemoMethodTestDescriptor(UniqueId.root("method", uniqueId), testClass, method); + } + + // ------------------------------------------------------------------------- + + private static class Class1 { + @Test + void test1() { + } + + @Test + void test2() { + } + } + + private static class Class2 { + @Test + void test1() { + } + + @Test + void test2() { + } + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java b/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java index e863f8c56d67..cf428c20a5d2 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/PostDiscoveryFilterStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java index 0c1b54e9b4d7..d81e7879ef0b 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TagFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java index 745550c514f6..7f39c87a6f54 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TagIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -85,6 +85,7 @@ private void executeTaggedTestCase(PostDiscoveryFilter filter) { .execute(); } + @SuppressWarnings("JUnitMalformedDeclaration") static class TaggedTestCase { static boolean tag1WasExecuted = false; diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java index 7ba6716495e6..0b845babfda9 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestIdentifierTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java index 93e91f168108..cf6a437b3928 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherDiscoveryListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor1.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor1.java index a077c68afad3..3f1007458293 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor1.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor1.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor2.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor2.java index 6adbd6a56e35..b19195c466b4 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor2.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherInterceptor2.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java index e239effb560b..04256b97b4d6 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestLauncherSessionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java index e62df473bb38..22be5ce5d260 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestPlanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,7 @@ package org.junit.platform.launcher; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -40,7 +41,7 @@ public Type getType() { } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); assertThat(testPlan.containsTests()).as("contains tests").isFalse(); } @@ -55,7 +56,7 @@ public Type getType() { } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @@ -75,7 +76,7 @@ public boolean mayRegisterTests() { } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); assertThat(testPlan.containsTests()).as("contains tests").isTrue(); } @@ -93,7 +94,8 @@ void acceptsVisitorsInDepthFirstOrder() { engineDescriptor2.addChild(test2); engineDescriptor2.addChild(test3); - var testPlan = TestPlan.from(List.of(engineDescriptor, engineDescriptor2), configParams); + var testPlan = TestPlan.from(List.of(engineDescriptor, engineDescriptor2), configParams, + dummyOutputDirectoryProvider()); var visitor = mock(TestPlan.Visitor.class); testPlan.accept(visitor); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java b/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java index 7b79b2e4ab49..f03091c3d7d3 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/TestPostDiscoveryTagFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java new file mode 100644 index 000000000000..c15ad7aa543f --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ClasspathAlignmentCheckerTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package org.junit.platform.launcher.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.ClasspathAlignmentChecker.WELL_KNOWN_PACKAGES; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.regex.Pattern; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.PackageInfo; + +import org.junit.jupiter.api.Test; + +class ClasspathAlignmentCheckerTests { + + @Test + void classpathIsAligned() { + assertThat(ClasspathAlignmentChecker.check(new LinkageError())).isEmpty(); + } + + @Test + void wrapsLinkageErrorForUnalignedClasspath() { + var cause = new LinkageError(); + AtomicInteger counter = new AtomicInteger(); + Function packageLookup = name -> { + var pkg = mock(Package.class); + when(pkg.getName()).thenReturn(name); + when(pkg.getImplementationVersion()).thenReturn(counter.incrementAndGet() + ".0.0"); + return pkg; + }; + + var result = ClasspathAlignmentChecker.check(cause, packageLookup); + + assertThat(result).isPresent(); + assertThat(result.get()) // + .hasMessageStartingWith("The wrapped LinkageError is likely caused by the versions of " + + "JUnit jars on the classpath/module path not being properly aligned.") // + .hasMessageContaining("Please ensure consistent versions are used") // + .hasMessageFindingMatch("https://junit\\.org/junit5/docs/.*/user-guide/#dependency-metadata") // + .hasMessageContaining("The following conflicting versions were detected:") // + .hasMessageContaining("- org.junit.jupiter.api: 1.0.0") // + .hasMessageContaining("- org.junit.jupiter.engine: 2.0.0") // + .cause().isSameAs(cause); + } + + @Test + void allRootPackagesAreChecked() { + var allowedFileNames = Pattern.compile("junit-(?:platform|jupiter|vintage)-.+[\\d.]+(?:-SNAPSHOT)?\\.jar"); + var classGraph = new ClassGraph() // + .acceptPackages("org.junit.platform", "org.junit.jupiter", "org.junit.vintage") // + .rejectPackages("org.junit.platform.reporting.shadow", "org.junit.jupiter.params.shadow") // + .filterClasspathElements(e -> { + var path = Path.of(e); + var fileName = path.getFileName().toString(); + return allowedFileNames.matcher(fileName).matches(); + }); + + try (var scanResult = classGraph.scan()) { + var foundPackages = scanResult.getPackageInfo().stream() // + .filter(it -> !it.getClassInfo().isEmpty()) // + .map(PackageInfo::getName) // + .sorted() // + .toList(); + + assertThat(foundPackages) // + .allMatch(name -> WELL_KNOWN_PACKAGES.stream().anyMatch(name::startsWith)); + assertThat(WELL_KNOWN_PACKAGES) // + .allMatch(name -> foundPackages.stream().anyMatch(it -> it.startsWith(name))); + } + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java index d56669e6eb80..575f4407c24d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeEngineExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -28,6 +28,7 @@ import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.descriptor.DemoMethodTestDescriptor; import org.mockito.InOrder; @@ -171,6 +172,11 @@ public void executionFinished(TestDescriptor testDescriptor, TestExecutionResult public void reportingEntryPublished(TestDescriptor testDescriptor, ReportEntry entry) { throw new RuntimeException("failed to invoke listener"); } + + @Override + public void fileEntryPublished(TestDescriptor testDescriptor, FileEntry file) { + throw new RuntimeException("failed to invoke listener"); + } } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java index cf3004e23966..ebb6357891b8 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/CompositeTestExecutionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -206,7 +207,7 @@ private void assertThatTestListenerErrorLogged(LogRecordListener logRecordListen } private static TestPlan anyTestPlan() { - return TestPlan.from(Set.of(anyTestDescriptor()), mock()); + return TestPlan.from(Set.of(anyTestDescriptor()), mock(), dummyOutputDirectoryProvider()); } private static DemoMethodTestDescriptor anyTestDescriptor() { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java index 7f473923ec20..04c9492a09ce 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/DefaultLauncherEngineFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 0f1a3d50f76d..e1f240617575 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -197,7 +197,7 @@ public void execute(ExecutionRequest request) { assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); + .cause().isSameAs(rootCause); } @Test @@ -221,7 +221,7 @@ public void execute(ExecutionRequest request) { assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); + .cause().isSameAs(rootCause); } @Test @@ -245,7 +245,7 @@ public void execute(ExecutionRequest request) { assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); + .cause().isSameAs(rootCause); } @Test @@ -271,7 +271,7 @@ public void execute(ExecutionRequest request) { assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause); + .cause().isSameAs(rootCause); } @Test @@ -298,8 +298,8 @@ public void execute(ExecutionRequest request) { assertThat(testExecutionResult.getValue().getThrowable()).isPresent(); assertThat(testExecutionResult.getValue().getThrowable().get()) // .hasMessage("TestEngine with ID 'TestEngineStub' failed to execute tests") // - .hasCauseReference(rootCause) // - .hasSuppressedException(originalFailure); + .hasSuppressedException(originalFailure) // + .cause().isSameAs(rootCause); } @Test diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java index 8744124def16..854c3bcefdf4 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/EngineDiscoveryResultValidatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java index 0d2d5786687d..32b2087ed966 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ExecutionListenerAdapterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,7 @@ package org.junit.platform.launcher.core; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.util.Map; @@ -34,7 +35,8 @@ class ExecutionListenerAdapterTests { void testReportingEntryPublished() { var testDescriptor = getSampleMethodTestDescriptor(); - var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(), testDescriptor), mock()); + var discoveryResult = new LauncherDiscoveryResult(Map.of(mock(), testDescriptor), mock(), + dummyOutputDirectoryProvider()); var testPlan = InternalTestPlan.from(discoveryResult); var testIdentifier = testPlan.getTestIdentifier(testDescriptor.getUniqueId()); diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProviderTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProviderTests.java new file mode 100644 index 000000000000..7ba1efb5fba6 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/HierarchicalOutputDirectoryProviderTests.java @@ -0,0 +1,83 @@ +/* + * 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.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.nio.file.Path; +import java.util.function.Supplier; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.engine.UniqueId; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoSettings; + +@MockitoSettings +public class HierarchicalOutputDirectoryProviderTests { + + @TempDir + Path tempDir; + + @Mock + Supplier rootDirSupplier; + + @Mock + TestDescriptor testDescriptor; + + @InjectMocks + HierarchicalOutputDirectoryProvider provider; + + @BeforeEach + void prepareMock() { + when(rootDirSupplier.get()).thenReturn(tempDir); + } + + @Test + void returnsConfiguredRootDir() { + assertThat(provider.getRootDirectory()).isEqualTo(tempDir); + assertThat(provider.getRootDirectory()).isEqualTo(tempDir); + verify(rootDirSupplier, times(1)).get(); + } + + @Test + void createsSubDirectoriesBasedOnUniqueId() throws Exception { + var uniqueId = UniqueId.forEngine("engine") // + .append("irrelevant", "foo") // + .append("irrelevant", "bar"); + when(testDescriptor.getUniqueId()).thenReturn(uniqueId); + + var outputDir = provider.createOutputDirectory(testDescriptor); + + assertThat(outputDir) // + .isEqualTo(tempDir.resolve(Path.of("engine", "foo", "bar"))) // + .exists(); + } + + @Test + void replacesForbiddenCharacters() throws Exception { + var uniqueId = UniqueId.forEngine("Engine<>") // + .append("irrelevant", "*/abc"); + when(testDescriptor.getUniqueId()).thenReturn(uniqueId); + + var outputDir = provider.createOutputDirectory(testDescriptor); + + assertThat(outputDir) // + .isEqualTo(tempDir.resolve(Path.of("Engine__", "__abc"))) // + .exists(); + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java index 5471b0214be1..c3b518dcd3b2 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java index 3ef5cb275ee4..2bd8b5aea1da 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -266,6 +266,7 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext contex } } + @SuppressWarnings("JUnitMalformedDeclaration") @ExtendWith(Mutator.class) static class Something { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java index 3ec50bc52dc0..93d9cd4a771f 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherDiscoveryRequestBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.platform.launcher.core; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -64,7 +63,7 @@ void modulesAreStoredInDiscoveryRequest() { // @formatter:on var packageSelectors = discoveryRequest.getSelectorsByType(ModuleSelector.class).stream().map( - ModuleSelector::getModuleName).collect(toList()); + ModuleSelector::getModuleName).toList(); assertThat(packageSelectors).contains("java.base"); } @@ -78,7 +77,7 @@ void packagesAreStoredInDiscoveryRequest() { // @formatter:on var packageSelectors = discoveryRequest.getSelectorsByType(PackageSelector.class).stream().map( - PackageSelector::getPackageName).collect(toList()); + PackageSelector::getPackageName).toList(); assertThat(packageSelectors).contains("org.junit.platform.engine"); } @@ -93,8 +92,9 @@ void classesAreStoredInDiscoveryRequest() { .build(); // @formatter:on - List> classes = discoveryRequest.getSelectorsByType(ClassSelector.class).stream().map( - ClassSelector::getJavaClass).collect(toList()); + @SuppressWarnings("rawtypes") + List classes = discoveryRequest.getSelectorsByType(ClassSelector.class).stream()// + .map(ClassSelector::getJavaClass).map(Class.class::cast).toList(); assertThat(classes).contains(SampleTestClass.class, LauncherDiscoveryRequestBuilderTests.class); } @@ -167,7 +167,7 @@ void uniqueIdsAreStoredInDiscoveryRequest() { // @formatter:on var uniqueIds = discoveryRequest.getSelectorsByType(UniqueIdSelector.class).stream().map( - UniqueIdSelector::getUniqueId).map(Object::toString).collect(toList()); + UniqueIdSelector::getUniqueId).map(Object::toString).toList(); assertThat(uniqueIds).contains(id1.toString(), id2.toString()); } @@ -398,6 +398,7 @@ void createsCompositeForMultipleListeners() { } + @SuppressWarnings("JUnitMalformedDeclaration") private static class SampleTestClass { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java index 69dba3c41872..06f6c308242d 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.platform.launcher.core; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -25,11 +24,14 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.LogRecord; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.fixtures.TrackLogRecords; import org.junit.jupiter.engine.JupiterTestEngine; import org.junit.platform.commons.PreconditionViolationException; +import org.junit.platform.commons.logging.LogRecordListener; import org.junit.platform.engine.EngineDiscoveryRequest; import org.junit.platform.engine.ExecutionRequest; import org.junit.platform.engine.TestDescriptor; @@ -80,7 +82,8 @@ void testExecutionListenerIsLoadedViaServiceApi() { } @Test - void testExecutionListenersExcludedViaConfigParametersIsNotLoadedViaServiceApi() { + void testExecutionListenersExcludedViaConfigParametersIsNotLoadedViaServiceApi( + @TrackLogRecords LogRecordListener listener) { withTestServices(() -> { var value = "org.junit.*.launcher.listeners.Unused*,org.junit.*.launcher.listeners.AnotherUnused*"; withSystemProperty(DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME, value, () -> { @@ -95,6 +98,16 @@ void testExecutionListenersExcludedViaConfigParametersIsNotLoadedViaServiceApi() launcher.execute(request().build()); + var logMessage = listener.stream(ServiceLoaderRegistry.class) // + .map(LogRecord::getMessage) // + .filter(it -> it.startsWith("Loaded TestExecutionListener instances")) // + .findAny(); + assertThat(logMessage).isPresent(); + assertThat(logMessage.get()) // + .contains("NoopTestExecutionListener@") // + .endsWith(" (excluded classes: [" + UnusedTestExecutionListener.class.getName() + ", " + + AnotherUnusedTestExecutionListener.class.getName() + "])"); + assertFalse(UnusedTestExecutionListener.called); assertFalse(AnotherUnusedTestExecutionListener.called); }); @@ -112,7 +125,7 @@ void create() { // @formatter:off var ids = roots.stream() .map(TestIdentifier::getUniqueId) - .collect(toList()); + .toList(); // @formatter:on assertThat(ids).containsOnly("[engine:junit-vintage]", "[engine:junit-jupiter]", @@ -135,7 +148,7 @@ void createWithConfig() { // @formatter:off var ids = roots.stream() .map(TestIdentifier::getUniqueId) - .collect(toList()); + .toList(); // @formatter:on assertThat(ids).containsOnly("[engine:junit-jupiter]"); @@ -361,7 +374,7 @@ private LauncherDiscoveryRequest createLauncherDiscoveryRequestForBothStandardEn // @formatter:on } - @SuppressWarnings("NewClassNamingConvention") + @SuppressWarnings({ "NewClassNamingConvention", "JUnitMalformedDeclaration" }) public static class JUnit4Example { @org.junit.Test @@ -370,7 +383,7 @@ public void testJ4() { } - @SuppressWarnings("NewClassNamingConvention") + @SuppressWarnings({ "NewClassNamingConvention", "JUnitMalformedDeclaration" }) static class JUnit5Example { @Tag("test-post-discovery") diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java index 95e38a9c733c..70e2cb5c6814 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherSessionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java index 2c73ee57e198..c95b2ea1fd2f 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/ListenerRegistryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java index 01aacc458db0..ee7e27cae5cd 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptingTestExecutionListenerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java index d524f5b7d283..2b5f35ca86bb 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/StreamInterceptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,6 +20,7 @@ import java.io.PrintStream; import java.util.stream.IntStream; +import org.junit.jupiter.api.AutoClose; import org.junit.jupiter.api.Test; /** @@ -27,17 +28,19 @@ */ class StreamInterceptorTests { - private ByteArrayOutputStream originalOut = new ByteArrayOutputStream(); - private PrintStream targetStream = new PrintStream(originalOut); + final ByteArrayOutputStream originalOut = new ByteArrayOutputStream(); + PrintStream targetStream = new PrintStream(originalOut); + + @AutoClose + StreamInterceptor streamInterceptor; @Test void interceptsWriteOperationsToStreamPerThread() { - var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 3).orElseThrow(RuntimeException::new); // @formatter:off IntStream.range(0, 1000) .parallel() - .peek(i -> targetStream.println(i)) .mapToObj(String::valueOf) .peek(i -> streamInterceptor.capture()) .peek(i -> targetStream.println(i)) @@ -49,7 +52,7 @@ void interceptsWriteOperationsToStreamPerThread() { void unregisterRestoresOriginalStream() { var originalStream = targetStream; - var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 3).orElseThrow(RuntimeException::new); assertSame(streamInterceptor, targetStream); @@ -61,8 +64,8 @@ void unregisterRestoresOriginalStream() { void writeForwardsOperationsToOriginalStream() throws IOException { var originalStream = targetStream; - StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 2).orElseThrow( - RuntimeException::new); + streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + 2).orElseThrow(RuntimeException::new); assertNotSame(originalStream, targetStream); targetStream.write('a'); @@ -73,7 +76,7 @@ void writeForwardsOperationsToOriginalStream() throws IOException { @Test void handlesNestedCaptures() { - var streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, 100).orElseThrow(RuntimeException::new); String outermost, inner, innermost; @@ -100,4 +103,19 @@ void handlesNestedCaptures() { () -> assertEquals("innermost", innermost) // ); } + + @Test + void capturesOutputFromNonTestThreads() throws Exception { + streamInterceptor = StreamInterceptor.register(targetStream, newStream -> this.targetStream = newStream, + 100).orElseThrow(RuntimeException::new); + + streamInterceptor.capture(); + var thread = new Thread(() -> { + targetStream.println("from non-test thread"); + }); + thread.start(); + thread.join(); + + assertEquals("from non-test thread", streamInterceptor.consume().trim()); + } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java index 7d6dcc2b8326..8df258c9b277 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/AnotherUnusedTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java index abe2e5f89f82..fe7c18cdc0fe 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/LoggingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java index 8d21878f1dc2..e36ec8b76840 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/NoopTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java index ecab5c1b90c5..0699c10fe69a 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/OutputDirTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,77 +10,92 @@ package org.junit.platform.launcher.listeners; +import static org.assertj.core.api.Assertions.as; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.STRING; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Optional; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.launcher.LauncherConstants; class OutputDirTests { + @TempDir + Path cwd; + @Test void getOutputDirUsesCustomOutputDir() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - Path outputDir = OutputDir.create(Optional.of(customDir)).toPath(); - assertThat(Files.isSameFile(Paths.get(customDir), outputDir)).isTrue(); + var customDir = cwd.resolve("custom-dir"); + var outputDir = OutputDir.create(Optional.of(customDir.toAbsolutePath().toString())).toPath(); + assertThat(Files.isSameFile(customDir, outputDir)).isTrue(); assertThat(outputDir).exists(); } + @Test + void getOutputDirUsesCustomOutputDirWithPlaceholder() { + var customDir = cwd.resolve("build").resolve("junit-" + LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER); + var outputDir = OutputDir.create(Optional.of(customDir.toAbsolutePath().toString())).toPath(); + assertThat(outputDir).exists() // + .hasParent(cwd.resolve("build")) // + .extracting(it -> it.getFileName().toString(), as(STRING)) // + .matches("junit-\\d+"); + } + @Test void getOutputDirFallsBackToCurrentWorkingDir() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking"; - String expected = cwd; + var expected = cwd; - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsMavenPom() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/maven"; - String expected = cwd + "/target"; + Files.createFile(cwd.resolve("pom.xml")); + var expected = cwd.resolve("target"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleGroovyDefaultBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("build.gradle")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleGroovyCustomBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/groovy/sub-project"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("sub-project.gradle")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleKotlinDefaultBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("build.gradle.kts")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } @Test void getOutputDirDetectsGradleKotlinCustomBuildScript() throws Exception { - String cwd = "src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project"; - String expected = cwd + "/build"; + Files.createFile(cwd.resolve("sub-project.gradle.kts")); + var expected = cwd.resolve("build"); - assertOutputDirIsDetected(cwd, expected); + assertOutputDirIsDetected(expected); } - private void assertOutputDirIsDetected(String cwd, String expected) throws IOException { - Path outputDir = OutputDir.createSafely(Optional.empty(), () -> Paths.get(cwd)).toPath(); - assertThat(Files.isSameFile(Paths.get(expected), outputDir)).isTrue(); + private void assertOutputDirIsDetected(Path expected) throws IOException { + var outputDir = OutputDir.createSafely(Optional.empty(), () -> cwd).toPath(); + assertThat(Files.isSameFile(expected, outputDir)).isTrue(); assertThat(outputDir).exists(); } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java index 3eeb506f44f1..adda779701d0 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/SummaryGenerationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.platform.commons.test.ConcurrencyTestingUtils.executeConcurrently; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.io.PrintWriter; @@ -39,7 +40,7 @@ class SummaryGenerationTests { private final SummaryGeneratingListener listener = new SummaryGeneratingListener(); - private final TestPlan testPlan = TestPlan.from(List.of(), mock()); + private final TestPlan testPlan = TestPlan.from(List.of(), mock(), dummyOutputDirectoryProvider()); @Test void emptyReport() { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java index 9d56bddc5761..dd916c324636 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UniqueIdTrackingListenerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -23,6 +23,7 @@ import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.LISTENER_ENABLED_PROPERTY_NAME; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_DIR_PROPERTY_NAME; import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.OUTPUT_FILE_PREFIX_PROPERTY_NAME; +import static org.junit.platform.launcher.listeners.UniqueIdTrackingListener.WORKING_DIR_PROPERTY_NAME; import static org.junit.platform.testkit.engine.Event.byTestDescriptor; import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; import static org.junit.platform.testkit.engine.EventConditions.event; @@ -35,7 +36,6 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -44,15 +44,16 @@ import java.util.stream.Stream; import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.io.TempDir; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.TestExecutionResult; import org.junit.platform.engine.discovery.ClassSelector; -import org.junit.platform.launcher.LauncherDiscoveryRequest; import org.junit.platform.launcher.TestExecutionListener; import org.junit.platform.launcher.TestIdentifier; import org.junit.platform.launcher.TestPlan; @@ -88,6 +89,15 @@ class UniqueIdTrackingListenerIntegrationTests { private static final String[] expectedConcurrentUniqueIds = { testA, testB, testC, testD, testE, testF }; + @TempDir + Path workingDir; + + @BeforeEach + void createFakeGradleFiles() throws Exception { + Files.createFile(workingDir.resolve("build.gradle")); + Files.createDirectory(workingDir.resolve("build")); + } + @Test void confirmExpectedUniqueIdsViaEngineTestKit() { // @formatter:off @@ -116,28 +126,18 @@ private Condition uniqueId(String uniqueId) { @Test void listenerIsRegisteredButDisabledByDefault() throws Exception { - long numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// + var numListenersRegistered = ServiceLoader.load(TestExecutionListener.class).stream()// .filter(provider -> UniqueIdTrackingListener.class.equals(provider.type()))// .count(); assertThat(numListenersRegistered).isEqualTo(1); - String outputDir = "build"; - String prefix = DEFAULT_OUTPUT_FILE_PREFIX; + var actualUniqueIds = executeTests(Map.of()); - deleteFiles(outputDir, prefix); + // Sanity check using the results of our local TestExecutionListener + assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); - try { - List actualUniqueIds = executeTests(Map.of()); - - // Sanity check using the results of our local TestExecutionListener - assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); - - // Check that files were not generated by the UniqueIdTrackingListener - assertThat(findFiles(outputDir, prefix)).isEmpty(); - } - finally { - deleteFiles(outputDir, prefix); - } + // Check that files were not generated by the UniqueIdTrackingListener + assertThat(findFiles(workingDir, DEFAULT_OUTPUT_FILE_PREFIX)).isEmpty(); } @Test @@ -147,20 +147,20 @@ void verifyUniqueIdsAreTrackedWithDefaults() throws Exception { @Test void verifyUniqueIdsAreTrackedWithCustomOutputFile() throws Exception { - String customPrefix = "test_ids"; + var customPrefix = "test_ids"; verifyUniqueIdsAreTracked("build", customPrefix, Map.of(OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputDir() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + var customDir = "build/UniqueIdTrackingListenerIntegrationTests"; verifyUniqueIdsAreTracked(customDir, DEFAULT_OUTPUT_FILE_PREFIX, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir)); } @Test void verifyUniqueIdsAreTrackedWithCustomOutputFileAndCustomOutputDir() throws Exception { - String customPrefix = "test_ids"; - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; + var customPrefix = "test_ids"; + var customDir = "build/UniqueIdTrackingListenerIntegrationTests"; verifyUniqueIdsAreTracked(customDir, customPrefix, Map.of(OUTPUT_DIR_PROPERTY_NAME, customDir, OUTPUT_FILE_PREFIX_PROPERTY_NAME, customPrefix)); @@ -172,59 +172,45 @@ private void verifyUniqueIdsAreTracked(String outputDir, String prefix, Map(configurationParameters); configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); - deleteFiles(outputDir, prefix); - - try { - List actualUniqueIds = executeTests(configurationParameters); + var actualUniqueIds = executeTests(configurationParameters); - // Sanity check using the results of our local TestExecutionListener - assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); + // Sanity check using the results of our local TestExecutionListener + assertThat(actualUniqueIds).containsExactlyInAnyOrder(expectedUniqueIds); - // Check contents of the file (or files) generated by the UniqueIdTrackingListener - assertThat(readAllFiles(outputDir, prefix)).containsExactlyInAnyOrder(expectedUniqueIds); - } - finally { - deleteFiles(outputDir, prefix); - } + // Check contents of the file (or files) generated by the UniqueIdTrackingListener + assertThat(readAllFiles(workingDir.resolve(outputDir), prefix)).containsExactlyInAnyOrder(expectedUniqueIds); } @Test void verifyUniqueIdsAreTrackedWithConcurrentlyExecutingTestPlans() throws Exception { - String customDir = "build/UniqueIdTrackingListenerIntegrationTests"; - String prefix = DEFAULT_OUTPUT_FILE_PREFIX; + var customDir = workingDir.resolve("build/UniqueIdTrackingListenerIntegrationTests"); + var prefix = DEFAULT_OUTPUT_FILE_PREFIX; Map configurationParameters = new HashMap<>(); configurationParameters.put(LISTENER_ENABLED_PROPERTY_NAME, "true"); - configurationParameters.put(OUTPUT_DIR_PROPERTY_NAME, customDir); - - deleteFiles(customDir, prefix); + configurationParameters.put(OUTPUT_DIR_PROPERTY_NAME, customDir.toAbsolutePath().toString()); - try { - Stream.of(TestCase2.class, TestCase3.class, TestCase4.class).parallel()// - .forEach(clazz -> executeTests(configurationParameters, selectClass(clazz))); + Stream.of(TestCase2.class, TestCase3.class, TestCase4.class).parallel()// + .forEach(clazz -> executeTests(configurationParameters, selectClass(clazz))); - // 3 output files should have been generated. - assertThat(findFiles(customDir, prefix)).hasSize(3); + // 3 output files should have been generated. + assertThat(findFiles(customDir, prefix)).hasSize(3); - // Check contents of the file (or files) generated by the UniqueIdTrackingListener - assertThat(readAllFiles(customDir, prefix)).containsExactlyInAnyOrder(expectedConcurrentUniqueIds); - } - finally { - deleteFiles(customDir, prefix); - } + // Check contents of the file (or files) generated by the UniqueIdTrackingListener + assertThat(readAllFiles(customDir, prefix)).containsExactlyInAnyOrder(expectedConcurrentUniqueIds); } - private static List executeTests(Map configurationParameters) { + private List executeTests(Map configurationParameters) { return executeTests(configurationParameters, selectClasses()); } - private static List executeTests(Map configurationParameters, - ClassSelector... classSelectors) { + private List executeTests(Map configurationParameters, ClassSelector... classSelectors) { List uniqueIds = new ArrayList<>(); - LauncherDiscoveryRequest request = request()// + var request = request()// .selectors(classSelectors)// .filters(includeEngines("junit-jupiter"))// .configurationParameters(configurationParameters)// + .configurationParameter(WORKING_DIR_PROPERTY_NAME, workingDir.toAbsolutePath().toString())// .build(); LauncherFactory.create().execute(request, new TestExecutionListener() { @@ -260,8 +246,7 @@ private static ClassSelector[] selectClasses() { selectClass(DisabledTestCase.class) }; } - private static Stream findFiles(String dir, String prefix) throws IOException { - Path outputDir = Paths.get(dir); + private static Stream findFiles(Path outputDir, String prefix) throws IOException { if (!Files.exists(outputDir)) { return Stream.empty(); } @@ -270,18 +255,7 @@ private static Stream findFiles(String dir, String prefix) throws IOExcept && path.getFileName().toString().startsWith(prefix))); } - private void deleteFiles(String outputDir, String prefix) throws IOException { - findFiles(outputDir, prefix).forEach(file -> { - try { - Files.deleteIfExists(file); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } - }); - } - - private Stream readAllFiles(String outputDir, String prefix) throws IOException { + private Stream readAllFiles(Path outputDir, String prefix) throws IOException { return findFiles(outputDir, prefix).map(outputFile -> { try { return Files.readAllLines(outputFile); @@ -294,6 +268,7 @@ private Stream readAllFiles(String outputDir, String prefix) throws IOEx // ------------------------------------------------------------------------- + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase1 { @Test @@ -305,6 +280,7 @@ void passingTest() { void skippedTest() { } + @SuppressWarnings("DataFlowIssue") @Test void abortedTest() { assumeTrue(false); @@ -321,6 +297,7 @@ Stream dynamicTests() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase2 { @Test @@ -332,6 +309,7 @@ void testB() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase3 { @Test @@ -343,6 +321,7 @@ void testD() { } } + @SuppressWarnings("JUnitMalformedDeclaration") static class TestCase4 { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java index b34399936e4c..ca9904096fa4 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/UnusedTestExecutionListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java index 223421d72da9..5c855acdaf39 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbortOnFailureLauncherDiscoveryListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -69,7 +69,7 @@ void abortsDiscoveryOnSelectorResolutionFailure() { assertThat(exception).hasMessage("TestEngine with ID 'some-engine' failed to discover tests"); assertThat(exception.getCause()) // .hasMessageEndingWith("resolution failed") // - .hasCauseReference(rootCause); + .cause().isSameAs(rootCause); } @Test @@ -90,7 +90,7 @@ public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId var exception = assertThrows(JUnitException.class, () -> launcher.discover(request)); assertThat(exception) // .hasMessage("TestEngine with ID 'some-engine' failed to discover tests") // - .hasCauseReference(rootCause); + .cause().isSameAs(rootCause); } } diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java index 060df776f852..f2ffad3215d4 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/AbstractLauncherDiscoveryListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListenerTests.java index 00fe416ec014..b0a1f5bbcf2c 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/CompositeLauncherDiscoveryListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java index 4ec465bfcbf3..86c7f5724999 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/discovery/LoggingLauncherDiscoveryListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListenerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListenerTests.java index 13e4ec36b440..cc262c54f2c2 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/listeners/session/CompositeLauncherSessionListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java index 43fb4e25dbda..648d624cac3c 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserErrorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java index 9fbab1299437..4d3133f66f27 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/ParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java index 8ef18564f46b..1133b03f5d7e 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TagExpressionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java index b8fca58da169..edee8f37edaf 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java index e882fe92d908..37cfb09786e6 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/tagexpression/TokenizerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ package org.junit.platform.launcher.tagexpression; -import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; @@ -87,7 +86,7 @@ private Stream rawStringsExtractedFrom(String expression) { } private List tokenStringsExtractedFrom(String expression) { - return tokensExtractedFrom(expression).map(Token::string).collect(toList()); + return tokensExtractedFrom(expression).map(Token::string).toList(); } private Stream tokensExtractedFrom(String expression) { diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java index 408a85ef9bc9..b8e5b25d5f41 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/LegacyReportingUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,6 +11,7 @@ package org.junit.platform.reporting.legacy; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.util.Set; @@ -71,13 +72,13 @@ void legacyReportingClassNameForDescendantOfTestIdentifierWithClassSourceIsClass } private String getClassName(UniqueId uniqueId) { - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider()); return LegacyReportingUtils.getClassName(testPlan, testPlan.getTestIdentifier(uniqueId)); } @SuppressWarnings("deprecation") private String getClassNameFromOldLocation(UniqueId uniqueId) { - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider()); return org.junit.platform.launcher.listeners.LegacyReportingUtils.getClassName(testPlan, testPlan.getTestIdentifier(uniqueId)); } diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java index a167ab629e6a..00db12731cd6 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/IncrementingClock.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java index de3b42a79f2f..a291ff90b51f 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/LegacyXmlReportGeneratingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; import static org.mockito.Mockito.mock; @@ -370,7 +371,7 @@ void printsExceptionWhenReportsDirCannotBeCreated() throws Exception { var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(reportsDir, new PrintWriter(out)); - listener.testPlanExecutionStarted(TestPlan.from(Set.of(), mock())); + listener.testPlanExecutionStarted(TestPlan.from(Set.of(), mock(), dummyOutputDirectoryProvider())); assertThat(out.toString()).containsSubsequence("Could not create reports directory", "FileAlreadyExistsException", "at "); @@ -386,7 +387,8 @@ void printsExceptionWhenReportCouldNotBeWritten() throws Exception { var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); - listener.testPlanExecutionStarted(TestPlan.from(Set.of(engineDescriptor), mock())); + listener.testPlanExecutionStarted( + TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider())); listener.executionFinished(TestIdentifier.from(engineDescriptor), successful()); assertThat(out.toString()).containsSubsequence("Could not write XML report", "Exception", "at "); @@ -397,7 +399,7 @@ void writesReportEntriesToSystemOutElement() throws Exception { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), mock()); + var testPlan = TestPlan.from(Set.of(engineDescriptor), mock(), dummyOutputDirectoryProvider()); var out = new StringWriter(); var listener = new LegacyXmlReportGeneratingListener(tempDirectory, new PrintWriter(out)); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java index fd220731d0b6..3f4e594dd9b9 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportAssertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java index 8d685654c1fa..c0e84ebdccbe 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportDataTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.TestExecutionResult.failed; import static org.junit.platform.engine.TestExecutionResult.successful; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.mockito.Mockito.mock; import java.time.Clock; @@ -37,7 +38,7 @@ void resultsOfTestIdentifierWithoutAnyReportedEventsAreEmpty() { var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var results = reportData.getResults(testPlan.getTestIdentifier(childUniqueId)); @@ -50,7 +51,7 @@ void resultsOfTestIdentifierWithoutReportedEventsContainsOnlyFailureOfAncestor() var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var failureOfAncestor = failed(new RuntimeException("failed!")); @@ -66,7 +67,7 @@ void resultsOfTestIdentifierWithoutReportedEventsContainsOnlySuccessOfAncestor() var engineDescriptor = new EngineDescriptor(UniqueId.forEngine("engine"), "Engine"); var childUniqueId = UniqueId.root("child", "test"); engineDescriptor.addChild(new TestDescriptorStub(childUniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(engineDescriptor.getUniqueId()), successful()); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java index b66b09e50e3c..0eebd933ffc4 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/legacy/xml/XmlReportWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,7 @@ package org.junit.platform.reporting.legacy.xml; +import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.joox.JOOX.$; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -19,7 +20,9 @@ import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY; import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.platform.reporting.legacy.xml.XmlReportAssertions.assertValidAccordingToJenkinsSchema; +import static org.junit.platform.reporting.legacy.xml.XmlReportWriter.ILLEGAL_CHARACTER_REPLACEMENT; import static org.mockito.Mockito.mock; import java.io.StringReader; @@ -28,6 +31,7 @@ import java.time.Clock; import java.util.Map; import java.util.Set; +import java.util.stream.IntStream; import java.util.stream.Stream; import org.joox.Match; @@ -54,7 +58,7 @@ class XmlReportWriterTests { @Test void writesTestsuiteElementsWithoutTestcaseElementsWithoutAnyTests() throws Exception { - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); @@ -72,7 +76,7 @@ void writesReportEntry() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); engineDescriptor.addChild(testDescriptor); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.addReportEntry(TestIdentifier.from(testDescriptor), ReportEntry.from("myKey", "myValue")); @@ -90,7 +94,7 @@ void writesCapturedOutput() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); var testDescriptor = new TestDescriptorStub(uniqueId, "successfulTest"); engineDescriptor.addChild(testDescriptor); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var reportEntry = ReportEntry.from(Map.of( // @@ -119,7 +123,7 @@ void writesCapturedOutput() throws Exception { void writesEmptySkippedElementForSkippedTestWithoutReason() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "skippedTest")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markSkipped(testPlan.getTestIdentifier(uniqueId), null); @@ -149,7 +153,7 @@ public String getLegacyReportingName() { return "failedTest"; } }); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(null)); @@ -169,7 +173,7 @@ public String getLegacyReportingName() { void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "failedTest")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(new NullPointerException())); @@ -186,7 +190,7 @@ void omitsMessageAttributeForFailedTestWithThrowableWithoutMessage() throws Exce void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError(""); @@ -202,7 +206,7 @@ void writesValidXmlEvenIfExceptionMessageContainsCData() throws Exception { void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); var assertionError = new AssertionError("expected: but was: "); @@ -219,50 +223,55 @@ void escapesInvalidCharactersInSystemPropertiesAndExceptionMessages() throws Exc assertValidAccordingToJenkinsSchema(testsuite.document()); assertThat(testsuite.find("property").matchAttr("name", "foo\\.bar").attr("value")) // - .isEqualTo(""); + .isEqualTo(String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)); var failure = testsuite.find("failure"); assertThat(failure.attr("message")) // - .isEqualTo("expected: but was: "); + .isEqualTo("expected: but was: "); assertThat(failure.text()) // - .contains("AssertionError: expected: but was: "); + .contains("AssertionError: expected: but was: "); + } + + @ParameterizedTest(name = "[{index}]") + @MethodSource("stringPairs") + void replacesIllegalCharacters(String input, String output) { + assertEquals(output, XmlReportWriter.replaceIllegalCharacters(input)); } @Test - void doesNotReopenCDataWithinCDataContent() throws Exception { + void writesValidXmlForExceptionMessagesContainingLineBreaks() throws Exception { var uniqueId = engineDescriptor.getUniqueId().append("test", "test"); engineDescriptor.addChild(new TestDescriptorStub(uniqueId, "test")); - var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams); + var testPlan = TestPlan.from(Set.of(engineDescriptor), configParams, dummyOutputDirectoryProvider()); + var allWhitespaceCharacters = IntStream.range(0, 0x10000) // + .filter(Character::isWhitespace) // + .filter(XmlReportWriter::isAllowedXmlCharacter) // + .mapToObj(Character::toString) // + .collect(joining()); + + var message = "a" + allWhitespaceCharacters + " b<&>"; var reportData = new XmlReportData(testPlan, Clock.systemDefaultZone()); - var assertionError = new AssertionError(""); + var assertionError = new AssertionError(message); reportData.markFinished(testPlan.getTestIdentifier(uniqueId), failed(assertionError)); - Writer assertingWriter = new StringWriter() { - @SuppressWarnings("NullableProblems") - @Override - public void write(char[] buffer, int off, int len) { - assertThat(new String(buffer, off, len)).doesNotContain("]]> stringPairs() { return Stream.of( // - arguments("\0", "�"), // - arguments("\1", ""), // + arguments("\0", String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)), // + arguments("\1", String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)), // arguments("\t", "\t"), // arguments("\r", "\r"), // arguments("\n", "\n"), // - arguments("\u001f", ""), // - arguments("\u0020", "\u0020"), // + arguments("\u001f", String.valueOf(ILLEGAL_CHARACTER_REPLACEMENT)), // + arguments("✅", "✅"), // + arguments(" ", " "), // arguments("foo!", "foo!"), // arguments("\uD801\uDC00", "\uD801\uDC00") // ); diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java new file mode 100644 index 000000000000..e13f7b44fce4 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/JUnitContributorTests.java @@ -0,0 +1,67 @@ +/* + * 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.reporting.open.xml; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.opentest4j.reporting.tooling.core.htmlreport.DefaultHtmlReportWriter; + +public class JUnitContributorTests { + + @Test + void contributesJUnitSpecificMetadata(@TempDir Path tempDir) throws Exception { + var xmlFile = Files.writeString(tempDir.resolve("report.xml"), + """ + + + + [engine:dummy] + dummy + CONTAINER + + + + + [engine:dummy]/[test:method] + method() + TEST + + + + + + + + + + """); + var htmlReport = tempDir.resolve("report.html"); + + new DefaultHtmlReportWriter().writeHtmlReport(List.of(xmlFile), htmlReport); + + assertThat(htmlReport).content() // + .contains("JUnit metadata") // + .contains("Type").contains("CONTAINER") // + .contains("Unique ID").contains("[engine:dummy]") // + .contains("Legacy reporting name").contains("dummy") // + .contains("Type").contains("TEST") // + .contains("Unique ID").contains("[engine:dummy]/[test:method]") // + .contains("Legacy reporting name").contains("method()"); + } +} diff --git a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java index 6a285f05c995..80a076abaa59 100644 --- a/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/reporting/open/xml/OpenTestReportGeneratingListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,29 +12,42 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -import static org.junit.jupiter.api.condition.JRE.JAVA_22; -import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDERR_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.CAPTURE_STDOUT_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_PROPERTY_NAME; +import static org.junit.platform.launcher.LauncherConstants.OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME; -import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.OUTPUT_DIR_PROPERTY_NAME; +import static org.junit.platform.reporting.testutil.FileUtils.findPath; -import java.io.IOException; +import java.io.PrintStream; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.commons.util.ExceptionUtils; import org.junit.platform.engine.TestEngine; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.FileEntry; import org.junit.platform.engine.reporting.ReportEntry; import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine; -import org.opentest4j.reporting.tooling.validator.DefaultValidator; -import org.opentest4j.reporting.tooling.validator.ValidationResult; +import org.junit.platform.tests.process.ProcessResult; +import org.junit.platform.tests.process.ProcessStarter; +import org.opentest4j.reporting.schema.Namespace; +import org.opentest4j.reporting.tooling.core.validator.DefaultValidator; +import org.opentest4j.reporting.tooling.core.validator.ValidationResult; import org.xmlunit.assertj3.XmlAssert; import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; @@ -43,29 +56,58 @@ * * @since 1.9 */ -@DisabledForJreRange(min = JAVA_22, disabledReason = "https://github.com/junit-team/junit5/issues/3594") public class OpenTestReportGeneratingListenerTests { - @TempDir(cleanup = ON_SUCCESS) - Path tempDirectory; + private PrintStream originalOut; + private PrintStream originalErr; + + @BeforeEach + void wrapSystemPrintStreams() { + // Work around nesting check in org.junit.platform.launcher.core.StreamInterceptor + originalOut = System.out; + System.setOut(new PrintStream(System.out)); + originalErr = System.err; + System.setErr(new PrintStream(System.err)); + } + + @AfterEach + void restoreSystemPrintStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } @Test - void writesValidXmlReport() throws Exception { + void writesValidXmlReport(@TempDir Path tempDirectory) throws Exception { var engine = new DemoHierarchicalTestEngine("dummy"); engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> { - var listener = context.request.getEngineExecutionListener(); - listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value")); + try { + var listener = context.request.getEngineExecutionListener(); + listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value")); + listener.fileEntryPublished(descriptor, FileEntry.from( + Files.writeString(tempDirectory.resolve("test.txt"), "Hello, world!"), "text/plain")); + System.out.println("Hello, stdout!"); + System.err.println("Hello, stderr!"); + } + catch (Throwable t) { + throw ExceptionUtils.throwAsUncheckedException(t); + } fail("failure message"); }); - executeTests(engine); + executeTests(tempDirectory, engine, tempDirectory.resolve("junit-" + OUTPUT_DIR_UNIQUE_NUMBER_PLACEHOLDER)); - var xmlFile = findXmlReport(); + var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml"); + assertThat(tempDirectory.relativize(xmlFile).toString()) // + .matches("junit-\\d+[/\\\\]open-test-report.xml"); assertThat(validate(xmlFile)).isEmpty(); var expected = """ - ${xmlunit.ignore} @@ -97,6 +139,17 @@ void writesValidXmlReport() throws Exception { + + + + + + + + + + + @@ -116,24 +169,87 @@ void writesValidXmlReport() throws Exception { .areIdentical(); } + @ParameterizedTest + @ValueSource(strings = { "https://github.com/junit-team/junit5.git", "git@github.com:junit-team/junit5.git" }) + void includesGitInfo(String originUrl, @TempDir Path tempDirectory) throws Exception { + + assumeTrue(tryExecGit(tempDirectory, "--version").exitCode() == 0, "git not installed"); + execGit(tempDirectory, "init", "--initial-branch=my_branch"); + execGit(tempDirectory, "remote", "add", "origin", originUrl); + + Files.writeString(tempDirectory.resolve("README.md"), "Hello, world!"); + execGit(tempDirectory, "add", "."); + + execGit(tempDirectory, "config", "user.name", "Alice"); + execGit(tempDirectory, "config", "user.email", "alice@example.org"); + execGit(tempDirectory, "commit", "-m", "Initial commit"); + + var engine = new DemoHierarchicalTestEngine("dummy"); + + executeTests(tempDirectory, engine, tempDirectory.resolve("junit-reports")); + + var xmlFile = findPath(tempDirectory, "glob:**/open-test-report.xml"); + assertThat(validate(xmlFile)).isEmpty(); + + var namespaceContext = Map.of("core", Namespace.REPORTING_CORE.getUri(), "e", + Namespace.REPORTING_EVENTS.getUri(), "git", Namespace.REPORTING_GIT.getUri()); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:repository/@originUrl") // + .isEqualTo(originUrl); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:branch") // + .isEqualTo("my_branch"); + + var commitHash = execGit(tempDirectory, "rev-parse", "--verify", "HEAD").stdOut().trim(); + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:commit") // + .isEqualTo(commitHash); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:status/@clean") // + .isEqualTo(false); + + XmlAssert.assertThat(xmlFile) // + .withNamespaceContext(namespaceContext) // + .valueByXPath("/e:events/core:infrastructure/git:status") // + .startsWith("?? junit-reports"); + } + + private static ProcessResult execGit(Path workingDir, String... arguments) throws InterruptedException { + var result = tryExecGit(workingDir, arguments); + assertEquals(0, result.exitCode(), "git " + String.join(" ", arguments) + " failed"); + return result; + } + + private static ProcessResult tryExecGit(Path workingDir, String... arguments) throws InterruptedException { + System.out.println("$ git " + String.join(" ", arguments)); + return new ProcessStarter() // + .executable(Path.of("git")) // + .workingDir(workingDir) // + .addArguments(arguments) // + .startAndWait(); + } + private ValidationResult validate(Path xmlFile) throws URISyntaxException { var catalogUri = requireNonNull(getClass().getResource("catalog.xml")).toURI(); return new DefaultValidator(catalogUri).validate(xmlFile); } - private void executeTests(TestEngine engine) { + private void executeTests(Path tempDirectory, TestEngine engine, Path outputDir) { var build = request() // .selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) // .configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) // - .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, tempDirectory.toString()) // + .configurationParameter(CAPTURE_STDOUT_PROPERTY_NAME, String.valueOf(true)) // + .configurationParameter(CAPTURE_STDERR_PROPERTY_NAME, String.valueOf(true)) // + .configurationParameter(OUTPUT_DIR_PROPERTY_NAME, outputDir.toString()) // .build(); - createLauncher(engine).execute(build, new OpenTestReportGeneratingListener()); - } - - private Path findXmlReport() throws IOException { - try (var stream = Files.list(tempDirectory)) { - return stream.findAny().orElseThrow(AssertionError::new); - } + createLauncher(engine).execute(build, new OpenTestReportGeneratingListener(tempDirectory)); } } diff --git a/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java b/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java index b9f54ef9d147..1a0f6190c21d 100644 --- a/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java +++ b/platform-tests/src/test/java/org/junit/platform/runner/JUnitPlatformRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -22,6 +22,7 @@ import static org.junit.platform.engine.TestExecutionResult.successful; import static org.junit.platform.engine.discovery.ClassNameFilter.STANDARD_INCLUDE_PATTERN; import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.dummyOutputDirectoryProvider; import static org.junit.runner.Description.createSuiteDescription; import static org.junit.runner.Description.createTestDescription; import static org.junit.runner.manipulation.Filter.matchMethodDescription; @@ -465,7 +466,7 @@ void convertsTestIdentifiersIntoDescriptions() { TestDescriptor container2 = new TestDescriptorStub(UniqueId.root("root", "container2"), "container2"); container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2a"), "test2a")); container2.addChild(new TestDescriptorStub(UniqueId.root("root", "test2b"), "test2b")); - var testPlan = TestPlan.from(List.of(container1, container2), mock()); + var testPlan = TestPlan.from(List.of(container1, container2), mock(), dummyOutputDirectoryProvider()); var launcher = mock(Launcher.class); when(launcher.discover(any())).thenReturn(testPlan); @@ -512,11 +513,12 @@ void appliesFilter() throws Exception { TestDescriptor originalParent2 = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2a"), "leaf2a")); originalParent2.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); - var fullTestPlan = TestPlan.from(List.of(originalParent1, originalParent2), configParams); + var fullTestPlan = TestPlan.from(List.of(originalParent1, originalParent2), configParams, + dummyOutputDirectoryProvider()); TestDescriptor filteredParent = new TestDescriptorStub(UniqueId.root("root", "parent2"), "parent2"); filteredParent.addChild(new TestDescriptorStub(UniqueId.root("root", "leaf2b"), "leaf2b")); - var filteredTestPlan = TestPlan.from(Set.of(filteredParent), configParams); + var filteredTestPlan = TestPlan.from(Set.of(filteredParent), configParams, dummyOutputDirectoryProvider()); var launcher = mock(Launcher.class); var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); @@ -539,7 +541,7 @@ void appliesFilter() throws Exception { @Test void throwsNoTestsRemainExceptionWhenNoTestIdentifierMatchesFilter() { var testPlan = TestPlan.from(Set.of(new TestDescriptorStub(UniqueId.root("root", "test"), "test")), - configParams); + configParams, dummyOutputDirectoryProvider()); var launcher = mock(Launcher.class); when(launcher.discover(any())).thenReturn(testPlan); @@ -777,7 +779,8 @@ private TestDescriptor testDescriptorWithTags(String... tag) { private LauncherDiscoveryRequest instantiateRunnerAndCaptureGeneratedRequest(Class testClass) { var launcher = mock(Launcher.class); var captor = ArgumentCaptor.forClass(LauncherDiscoveryRequest.class); - when(launcher.discover(captor.capture())).thenReturn(TestPlan.from(Set.of(), mock())); + when(launcher.discover(captor.capture())).thenReturn( + TestPlan.from(Set.of(), mock(), dummyOutputDirectoryProvider())); new JUnitPlatform(testClass, launcher); diff --git a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java b/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java index 5575d4996038..b2239ca97817 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/commons/SuiteLauncherDiscoveryRequestBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/BeforeAndAfterSuiteTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/BeforeAndAfterSuiteTests.java new file mode 100644 index 000000000000..d1032281d826 --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/BeforeAndAfterSuiteTests.java @@ -0,0 +1,229 @@ +/* + * 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.suite.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Named.named; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.suite.engine.SuiteEngineDescriptor.ENGINE_ID; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.FailingAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.FailingBeforeAndAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.FailingBeforeSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonStaticAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonStaticBeforeSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonVoidAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.NonVoidBeforeSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.ParameterAcceptingAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.ParameterAcceptingBeforeSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.PrivateAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.PrivateBeforeSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.SeveralFailingBeforeAndAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.SubclassWithBeforeAndAfterSuite; +import static org.junit.platform.suite.engine.testsuites.LifecycleMethodsSuites.SuccessfulBeforeAndAfterSuite; +import static org.junit.platform.testkit.engine.EventConditions.container; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import static org.junit.platform.testkit.engine.TestExecutionResultConditions.suppressed; + +import java.util.ArrayList; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.commons.JUnitException; +import org.junit.platform.suite.api.AfterSuite; +import org.junit.platform.suite.api.BeforeSuite; +import org.junit.platform.suite.engine.testcases.StatefulTestCase; +import org.junit.platform.testkit.engine.EngineExecutionResults; +import org.junit.platform.testkit.engine.EngineTestKit; + +/** + * Integration tests that verify support for {@link BeforeSuite} and {@link AfterSuite}, + * in the {@link SuiteTestEngine}. + * + * @since 1.11 + */ +public class BeforeAndAfterSuiteTests { + + @BeforeEach + void setUp() { + StatefulTestCase.callSequence = new ArrayList<>(); + } + + @Test + void successfulBeforeAndAfterSuite() { + // @formatter:off + executeSuite(SuccessfulBeforeAndAfterSuite.class) + .allEvents() + .assertStatistics(stats -> stats.started(7).finished(7).succeeded(6).failed(1)) + .assertThatEvents() + .haveExactly(1, event(test(StatefulTestCase.Test1.class.getName()), finishedSuccessfully())) + .haveExactly(1, event(test(StatefulTestCase.Test2.class.getName()), finishedWithFailure())); + + assertThat(StatefulTestCase.callSequence).containsExactly( + "beforeSuiteMethod", + "test1", + "test2", + "afterSuiteMethod" + ); + // @formatter:on + } + + @Test + void beforeAndAfterSuiteInheritance() { + // @formatter:off + executeSuite(SubclassWithBeforeAndAfterSuite.class) + .allEvents() + .assertStatistics(stats -> stats.started(7).finished(7).succeeded(6).failed(1)); + + assertThat(StatefulTestCase.callSequence).containsExactly( + "superclassBeforeSuiteMethod", + "subclassBeforeSuiteMethod", + "test1", + "test2", + "subclassAfterSuiteMethod", + "superclassAfterSuiteMethod" + ); + // @formatter:on + } + + @Test + void failingBeforeSuite() { + // @formatter:off + executeSuite(FailingBeforeSuite.class) + .allEvents() + .assertStatistics(stats -> stats.started(2).finished(2).succeeded(1).failed(1)) + .assertThatEvents() + .haveExactly(1, event( + container(FailingBeforeSuite.class), + finishedWithFailure(instanceOf(RuntimeException.class), + message("Exception thrown by @BeforeSuite method")))); + + assertThat(StatefulTestCase.callSequence).containsExactly( + "beforeSuiteMethod", + "afterSuiteMethod" + ); + // @formatter:on + } + + @Test + void failingAfterSuite() { + // @formatter:off + executeSuite(FailingAfterSuite.class) + .allEvents() + .assertStatistics(stats -> stats.started(7).finished(7).succeeded(5).failed(2)) + .assertThatEvents() + .haveExactly(1, event( + container(FailingAfterSuite.class), + finishedWithFailure(instanceOf(RuntimeException.class), + message("Exception thrown by @AfterSuite method")))); + + assertThat(StatefulTestCase.callSequence).containsExactly( + "beforeSuiteMethod", + "test1", + "test2", + "afterSuiteMethod" + ); + // @formatter:on + } + + @Test + void failingBeforeAndAfterSuite() { + // @formatter:off + executeSuite(FailingBeforeAndAfterSuite.class) + .allEvents() + .assertStatistics(stats -> stats.started(2).finished(2).succeeded(1).failed(1)) + .assertThatEvents() + .haveExactly(1, event( + container(FailingBeforeAndAfterSuite.class), + finishedWithFailure(instanceOf(RuntimeException.class), + message("Exception thrown by @BeforeSuite method"), + suppressed(0, instanceOf(RuntimeException.class), + message("Exception thrown by @AfterSuite method"))))); + + assertThat(StatefulTestCase.callSequence).containsExactly( + "beforeSuiteMethod", + "afterSuiteMethod" + ); + // @formatter:on + } + + @Test + void severalFailingBeforeAndAfterSuite() { + // @formatter:off + executeSuite(SeveralFailingBeforeAndAfterSuite.class) + .allEvents() + .assertStatistics(stats -> stats.started(2).finished(2).succeeded(1).failed(1)) + .assertThatEvents() + .haveExactly(1, event( + container(SeveralFailingBeforeAndAfterSuite.class), + finishedWithFailure(instanceOf(RuntimeException.class), + message("Exception thrown by @BeforeSuite method"), + suppressed(0, instanceOf(RuntimeException.class), + message("Exception thrown by @AfterSuite method")), + suppressed(1, instanceOf(RuntimeException.class), + message("Exception thrown by @AfterSuite method"))))); + + assertThat(StatefulTestCase.callSequence).containsExactly( + "beforeSuiteMethod", + "afterSuiteMethod", + "afterSuiteMethod" + ); + // @formatter:on + } + + @ParameterizedTest(name = "{0}") + @MethodSource + void invalidBeforeOrAfterSuiteMethod(Class testSuiteClass, Predicate failureMessagePredicate) { + // @formatter:off + executeSuite(testSuiteClass) + .allEvents() + .assertThatEvents() + .haveExactly(1, event( + container(testSuiteClass), + finishedWithFailure(instanceOf(JUnitException.class), message(failureMessagePredicate)))); + // @formatter:on + } + + private static Stream invalidBeforeOrAfterSuiteMethod() { + return Stream.of( + invalidBeforeOrAfterSuiteCase(NonVoidBeforeSuite.class, "@BeforeSuite method", "must not return a value."), + invalidBeforeOrAfterSuiteCase(ParameterAcceptingBeforeSuite.class, "@BeforeSuite method", + "must not accept parameters."), + invalidBeforeOrAfterSuiteCase(NonStaticBeforeSuite.class, "@BeforeSuite method", "must be static."), + invalidBeforeOrAfterSuiteCase(PrivateBeforeSuite.class, "@BeforeSuite method", "must not be private."), + invalidBeforeOrAfterSuiteCase(NonVoidAfterSuite.class, "@AfterSuite method", "must not return a value."), + invalidBeforeOrAfterSuiteCase(ParameterAcceptingAfterSuite.class, "@AfterSuite method", + "must not accept parameters."), + invalidBeforeOrAfterSuiteCase(NonStaticAfterSuite.class, "@AfterSuite method", "must be static."), + invalidBeforeOrAfterSuiteCase(PrivateAfterSuite.class, "@AfterSuite method", "must not be private.")); + } + + private static Arguments invalidBeforeOrAfterSuiteCase(Class suiteClass, String failureMessageStart, + String failureMessageEnd) { + return arguments(named(suiteClass.getSimpleName(), suiteClass), + (Predicate) s -> s.startsWith(failureMessageStart) && s.endsWith(failureMessageEnd)); + } + + private static EngineExecutionResults executeSuite(Class suiteClass) { + return EngineTestKit.engine(ENGINE_ID).selectors(selectClass(suiteClass)).execute(); + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java index 24d683c3397d..8041c89f1cf7 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteEngineTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,9 +10,11 @@ package org.junit.platform.suite.engine; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; import static org.junit.platform.launcher.TagFilter.excludeTags; +import static org.junit.platform.launcher.core.OutputDirectoryProviders.hierarchicalOutputDirectoryProvider; import static org.junit.platform.suite.engine.SuiteEngineDescriptor.ENGINE_ID; import static org.junit.platform.testkit.engine.EventConditions.container; import static org.junit.platform.testkit.engine.EventConditions.displayName; @@ -24,7 +26,10 @@ import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; +import java.nio.file.Path; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; @@ -65,11 +70,15 @@ */ class SuiteEngineTests { + @TempDir + private Path outputDir; + @Test void selectClasses() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectClassesSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -168,6 +177,7 @@ void suiteSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SuiteSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -184,6 +194,7 @@ void selectClassesByUniqueId() { .append(SuiteTestDescriptor.SEGMENT_TYPE, SelectClassesSuite.class.getName()); EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqId)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -239,6 +250,7 @@ void selectMethodAndSuiteInTestPlanByUniqueId() { EngineTestKit.engine(ENGINE_ID) .selectors(selectUniqueId(uniqueId)) .selectors(selectClass(SelectClassesSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -391,6 +403,7 @@ void cyclicSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(CyclicSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .allEvents() .assertThatEvents() @@ -417,6 +430,7 @@ void threePartCyclicSuite() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(ThreePartCyclicSuite.PartA.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .allEvents() .assertThatEvents() @@ -429,6 +443,7 @@ void selectByIdentifier() { // @formatter:off EngineTestKit.engine(ENGINE_ID) .selectors(selectClass(SelectByIdentifierSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) .execute() .testEvents() .assertThatEvents() @@ -437,6 +452,21 @@ void selectByIdentifier() { // @formatter:on } + @Test + void passesOutputDirectoryProviderToEnginesInSuite() { + // @formatter:off + EngineTestKit.engine(ENGINE_ID) + .selectors(selectClass(SelectClassesSuite.class)) + .outputDirectoryProvider(hierarchicalOutputDirectoryProvider(outputDir)) + .execute() + .testEvents() + .assertThatEvents() + .haveExactly(1, event(test(SingleTestTestCase.class.getName()), finishedSuccessfully())); + // @formatter:on + + assertThat(outputDir).isDirectoryRecursivelyContaining("glob:**/test.txt"); + } + @Suite @SelectClasses(SingleTestTestCase.class) private static class PrivateSuite { diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java index 02ad36b35029..0cc23198b237 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/SuiteTestDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -19,6 +19,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; import org.junit.jupiter.engine.descriptor.ClassTestDescriptor; import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor; import org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor; @@ -26,6 +27,8 @@ import org.junit.platform.engine.ConfigurationParameters; import org.junit.platform.engine.TestDescriptor; import org.junit.platform.engine.UniqueId; +import org.junit.platform.engine.reporting.OutputDirectoryProvider; +import org.junit.platform.launcher.core.OutputDirectoryProviders; import org.junit.platform.suite.api.Suite; import org.junit.platform.suite.engine.testcases.SingleTestTestCase; import org.junit.platform.suite.engine.testsuites.SelectClassesSuite; @@ -40,10 +43,13 @@ class SuiteTestDescriptorTests { final UniqueId jupiterEngineId = suiteId.append("engine", JupiterEngineDescriptor.ENGINE_ID); final UniqueId testClassId = jupiterEngineId.append(ClassTestDescriptor.SEGMENT_TYPE, SingleTestTestCase.class.getName()); - final UniqueId methodId = testClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, "test()"); + final UniqueId methodId = testClassId.append(TestMethodTestDescriptor.SEGMENT_TYPE, + "test(%s)".formatted(TestReporter.class.getName())); final ConfigurationParameters configurationParameters = new EmptyConfigurationParameters(); - final SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters); + final OutputDirectoryProvider outputDirectoryProvider = OutputDirectoryProviders.dummyOutputDirectoryProvider(); + final SuiteTestDescriptor suite = new SuiteTestDescriptor(suiteId, TestSuite.class, configurationParameters, + outputDirectoryProvider); @Test void suiteIsEmptyBeforeDiscovery() { diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/ConfigurationSensitiveTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/ConfigurationSensitiveTestCase.java index 1219e8c16251..926fef81e1cd 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/ConfigurationSensitiveTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/ConfigurationSensitiveTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java index 4d470c5b86e6..0854b1391f03 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/DynamicTestsTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java index 5ca4c08b960f..c16ba9d70c54 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyDynamicTestsTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java index 8b43ab0d8b0b..a03e57f434cd 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/EmptyTestTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java index 42348afa6dbd..a39cafc41d2e 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/JUnit4TestsTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java index 4a0b5a2a7813..cdb6f733acfa 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/MultipleTestsTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java index fb0c0230e18d..359a7d8a25d0 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/SingleTestTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,11 @@ package org.junit.platform.suite.engine.testcases; +import java.nio.file.Files; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestReporter; +import org.junit.jupiter.api.extension.MediaType; /** * @since 1.8 @@ -18,6 +22,7 @@ public class SingleTestTestCase { @Test - void test() { + void test(TestReporter testReporter) { + testReporter.publishFile("test.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.writeString(file, "test")); } } diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/StatefulTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/StatefulTestCase.java new file mode 100644 index 000000000000..b611bd9451ec --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/StatefulTestCase.java @@ -0,0 +1,48 @@ +/* + * 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.suite.engine.testcases; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * @since 1.11 + */ +public class StatefulTestCase { + + public static List callSequence = new ArrayList<>(); + + @SuppressWarnings("JUnitMalformedDeclaration") + public static class Test1 { + + @Test + void statefulTest() { + callSequence.add("test1"); + } + + } + + @SuppressWarnings("JUnitMalformedDeclaration") + public static class Test2 { + + @Test + void statefulTest() { + callSequence.add("test2"); + fail("This is a failing test"); + } + + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java index 5e726a4e8d91..962954b7c6ae 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testcases/TaggedTestTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java index b99d11bd7471..2dd69ed2532b 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/AbstractSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ConfigurationSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ConfigurationSuite.java index 8e7f6cba21b2..ccc1eb74040f 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ConfigurationSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ConfigurationSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java index 07274993fd56..9d958173bbfa 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/CyclicSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java index 37490756e8b9..238c1b6220b7 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/DynamicSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java index 05d8d4518aa8..49d4f7ff22e0 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyCyclicSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java index 6e547b19d6fb..b5a03def9112 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java index 619c98e2a9c3..5a8ab46f0b70 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyDynamicTestWithFailIfNoTestFalseSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java index 8bb833394339..436c60caf8fd 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java index 388dec7317c8..ea5ed9353a68 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/EmptyTestCaseWithFailIfNoTestFalseSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/LifecycleMethodsSuites.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/LifecycleMethodsSuites.java new file mode 100644 index 000000000000..bfbd4619d35d --- /dev/null +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/LifecycleMethodsSuites.java @@ -0,0 +1,245 @@ +/* + * 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.suite.engine.testsuites; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.platform.suite.api.AfterSuite; +import org.junit.platform.suite.api.BeforeSuite; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.engine.BeforeAndAfterSuiteTests; +import org.junit.platform.suite.engine.testcases.StatefulTestCase; + +/** + * Test suites used in {@link BeforeAndAfterSuiteTests}. + * + * @since 1.11 + */ +public class LifecycleMethodsSuites { + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Suite + @SelectClasses({ StatefulTestCase.Test1.class, StatefulTestCase.Test2.class }) + private @interface TestSuite { + } + + @TestSuite + public static class SuccessfulBeforeAndAfterSuite { + + @BeforeSuite + static void setUp() { + StatefulTestCase.callSequence.add("beforeSuiteMethod"); + } + + @AfterSuite + static void tearDown() { + StatefulTestCase.callSequence.add("afterSuiteMethod"); + } + + } + + @TestSuite + public static class FailingBeforeSuite { + + @BeforeSuite + static void setUp() { + StatefulTestCase.callSequence.add("beforeSuiteMethod"); + throw new RuntimeException("Exception thrown by @BeforeSuite method"); + } + + @AfterSuite + static void tearDown() { + StatefulTestCase.callSequence.add("afterSuiteMethod"); + } + + } + + @TestSuite + public static class FailingAfterSuite { + + @BeforeSuite + static void setUp() { + StatefulTestCase.callSequence.add("beforeSuiteMethod"); + } + + @AfterSuite + static void tearDown() { + StatefulTestCase.callSequence.add("afterSuiteMethod"); + throw new RuntimeException("Exception thrown by @AfterSuite method"); + } + + } + + @TestSuite + public static class FailingBeforeAndAfterSuite { + + @BeforeSuite + static void setUp() { + StatefulTestCase.callSequence.add("beforeSuiteMethod"); + throw new RuntimeException("Exception thrown by @BeforeSuite method"); + } + + @AfterSuite + static void tearDown() { + StatefulTestCase.callSequence.add("afterSuiteMethod"); + throw new RuntimeException("Exception thrown by @AfterSuite method"); + } + + } + + @TestSuite + public static class SeveralFailingBeforeAndAfterSuite { + + @BeforeSuite + static void setUp1() { + StatefulTestCase.callSequence.add("beforeSuiteMethod"); + throw new RuntimeException("Exception thrown by @BeforeSuite method"); + } + + @BeforeSuite + static void setUp2() { + StatefulTestCase.callSequence.add("beforeSuiteMethod"); + throw new RuntimeException("Exception thrown by @BeforeSuite method"); + } + + @AfterSuite + static void tearDown1() { + StatefulTestCase.callSequence.add("afterSuiteMethod"); + throw new RuntimeException("Exception thrown by @AfterSuite method"); + } + + @AfterSuite + static void tearDown2() { + StatefulTestCase.callSequence.add("afterSuiteMethod"); + throw new RuntimeException("Exception thrown by @AfterSuite method"); + } + + } + + @TestSuite + public static class SuperclassWithBeforeAndAfterSuite { + + @BeforeSuite + static void setUp() { + StatefulTestCase.callSequence.add("superclassBeforeSuiteMethod"); + } + + @AfterSuite + static void tearDown() { + StatefulTestCase.callSequence.add("superclassAfterSuiteMethod"); + } + + } + + public static class SubclassWithBeforeAndAfterSuite extends SuperclassWithBeforeAndAfterSuite { + + @BeforeSuite + static void setUp() { + StatefulTestCase.callSequence.add("subclassBeforeSuiteMethod"); + } + + @AfterSuite + static void tearDown() { + StatefulTestCase.callSequence.add("subclassAfterSuiteMethod"); + } + + } + + @TestSuite + public static class NonVoidBeforeSuite { + + @BeforeSuite + static String nonVoidBeforeSuite() { + fail("Should not be called"); + return ""; + } + + } + + @TestSuite + public static class ParameterAcceptingBeforeSuite { + + @BeforeSuite + static void parameterAcceptingBeforeSuite(String param) { + fail("Should not be called"); + } + + } + + @TestSuite + public static class NonStaticBeforeSuite { + + @BeforeSuite + void nonStaticBeforeSuite() { + fail("Should not be called"); + } + + } + + @TestSuite + public static class PrivateBeforeSuite { + + @BeforeSuite + private static void privateBeforeSuite() { + fail("Should not be called"); + } + + } + + @TestSuite + public static class NonVoidAfterSuite { + + @AfterSuite + static String nonVoidAfterSuite() { + fail("Should not be called"); + return ""; + } + + } + + @TestSuite + public static class ParameterAcceptingAfterSuite { + + @AfterSuite + static void parameterAcceptingAfterSuite(String param) { + fail("Should not be called"); + } + + } + + @TestSuite + public static class NonStaticAfterSuite { + + @AfterSuite + void nonStaticAfterSuite() { + fail("Should not be called"); + } + + } + + @TestSuite + public static class PrivateAfterSuite { + + @AfterSuite + private static void privateAfterSuite() { + fail("Should not be called"); + } + + } + +} diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java index 0546e6df7213..faa98e04cc6c 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultiEngineSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java index 1570efccb1fa..7208ff543635 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/MultipleSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java index 8f15386fb018..aa6581f3a791 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/NestedSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectByIdentifierSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectByIdentifierSuite.java index 5aad2b329d60..6ceb2ce905b1 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectByIdentifierSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectByIdentifierSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java index a2fac4e257e3..c2d5f58d4f0d 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectClassesSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java index c04b0869ee06..a01b26b08b7c 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SelectMethodsSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java index baa6db4f6476..c87116af45f7 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteDisplayNameSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java index 282092b8f842..32f55881f642 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/SuiteSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java index 133330816eb5..3890ff950101 100644 --- a/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java +++ b/platform-tests/src/test/java/org/junit/platform/suite/engine/testsuites/ThreePartCyclicSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java index 20d71f0c3def..55325c1962c4 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/EngineTestKitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -67,6 +67,7 @@ private Optional executeExampleTestCaseAndCollectValue(UnaryOperator stats.skipped(1).started(3).succeeded(1).aborted(1).failed(1)); } + @SuppressWarnings("JUnitMalformedDeclaration") static class ExampleTestCase { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java index e7faa286c983..d109969008b1 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/NestedContainerEventConditionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -123,6 +123,7 @@ void eventConditionsForMultipleLevelsOfNestedClasses() { // @formatter:on } + @SuppressWarnings("JUnitMalformedDeclaration") static class ATestCase { @Test diff --git a/platform-tests/src/test/java/org/junit/platform/testkit/engine/TestExecutionResultConditionsTests.java b/platform-tests/src/test/java/org/junit/platform/testkit/engine/TestExecutionResultConditionsTests.java index 9ea2079aedbf..c8e868b0ede4 100644 --- a/platform-tests/src/test/java/org/junit/platform/testkit/engine/TestExecutionResultConditionsTests.java +++ b/platform-tests/src/test/java/org/junit/platform/testkit/engine/TestExecutionResultConditionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tests/src/test/resources/junit-platform.properties b/platform-tests/src/test/resources/junit-platform.properties new file mode 100644 index 000000000000..6efc0d5e85ce --- /dev/null +++ b/platform-tests/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.extensions.autodetection.enabled=true diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle b/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/build.gradle +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle b/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/groovy/sub-project/sub-project.gradle +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts b/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/build.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts b/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts deleted file mode 100644 index a4185e83967b..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/gradle/kotlin/sub-project/sub-project.gradle.kts +++ /dev/null @@ -1 +0,0 @@ -// This file is used in tests for the UniqueIdTrackingListener diff --git a/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml b/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml deleted file mode 100644 index 1a462ec9525d..000000000000 --- a/platform-tests/src/test/resources/listeners/uidtracking/maven/pom.xml +++ /dev/null @@ -1 +0,0 @@ - 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 9b7d6c805f1b..9e5b70ba9ef2 100644 --- a/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts +++ b/platform-tooling-support-tests/platform-tooling-support-tests.gradle.kts @@ -1,3 +1,4 @@ + import com.gradle.develocity.agent.gradle.internal.test.TestDistributionConfigurationInternal import junitbuild.extensions.capitalized import org.gradle.api.tasks.PathSensitivity.RELATIVE @@ -12,7 +13,7 @@ plugins { } javaLibrary { - mainJavaVersion = JavaVersion.VERSION_11 + mainJavaVersion = JavaVersion.VERSION_21 } spotless { @@ -43,12 +44,17 @@ val mavenDistributionClasspath = configurations.resolvable("mavenDistributionCla } dependencies { - implementation(libs.bartholdy) { - because("manage external tool installations") - } implementation(libs.commons.io) { because("moving/deleting directory trees") } + implementation(projects.platformTests) { + capabilities { + requireFeature("process-starter") + } + } + implementation(projects.junitJupiterApi) { + because("it uses the OS enum to support Windows") + } testImplementation(libs.archunit) { because("checking the architecture of JUnit 5") @@ -56,9 +62,6 @@ dependencies { testImplementation(libs.apiguardian) { because("we validate that public classes are annotated") } - testImplementation(libs.groovy4) { - because("it provides convenience methods to handle process output") - } testImplementation(libs.bndlib) { because("parsing OSGi metadata") } @@ -70,12 +73,14 @@ dependencies { } testImplementation(libs.bundles.xmlunit) testImplementation(testFixtures(projects.junitJupiterApi)) + testImplementation(testFixtures(projects.junitPlatformReporting)) thirdPartyJars(libs.junit4) thirdPartyJars(libs.assertj) thirdPartyJars(libs.apiguardian) thirdPartyJars(libs.hamcrest) thirdPartyJars(libs.opentest4j) + thirdPartyJars(libs.openTestReporting.tooling.spi) thirdPartyJars(libs.jimfs) antJars(platform(projects.junitBom)) @@ -107,7 +112,7 @@ val normalizeMavenRepo by tasks.registering(Sync::class) { val tempRepoName: String by rootProject // All maven-aware projects must be published to the local temp repository - (mavenizedProjects + projects.junitBom.dependencyProject) + (mavenizedProjects + dependencyProject(projects.junitBom)) .map { project -> project.tasks.named("publishAllPublicationsTo${tempRepoName.capitalized()}Repository") } .forEach { dependsOn(it) } @@ -137,13 +142,16 @@ tasks.test { dependsOn(normalizeMavenRepo) jvmArgumentProviders += MavenRepo(project, normalizeMavenRepo.map { it.destinationDir }) } + environment.remove("JAVA_TOOL_OPTIONS") jvmArgumentProviders += JarPath(project, thirdPartyJarsClasspath.get(), "thirdPartyJars") jvmArgumentProviders += JarPath(project, antJarsClasspath.get(), "antJars") jvmArgumentProviders += MavenDistribution(project, unzipMavenDistribution, mavenDistributionDir) - (options as JUnitPlatformOptions).apply { - includeEngines("archunit") + if (buildParameters.javaToolchain.version.getOrElse(21) < 24) { + (options as JUnitPlatformOptions).apply { + includeEngines("archunit") + } } inputs.apply { @@ -157,6 +165,12 @@ tasks.test { dir("${rootDir}/documentation/src/test").withPathSensitivity(RELATIVE) } + // Disable capturing output since parallel execution is enabled and output of + // external processes happens on non-test threads which can't reliably be + // attributed to the test that started the process. + systemProperty("junit.platform.output.capture.stdout", "false") + systemProperty("junit.platform.output.capture.stderr", "false") + develocity { testDistribution { requirements.add("jdk=8") @@ -165,6 +179,10 @@ tasks.test { } } jvmArgumentProviders += JavaHomeDir(project, 8, develocity.testDistribution.enabled) + + val gradleJavaVersion = JavaVersion.current().majorVersion.toInt() + jvmArgumentProviders += JavaHomeDir(project, gradleJavaVersion, develocity.testDistribution.enabled) + systemProperty("gradle.java.version", gradleJavaVersion) } class MavenRepo(project: Project, @get:Internal val repoDir: Provider) : CommandLineArgumentProvider { diff --git a/platform-tooling-support-tests/projects/ant-starter/build.xml b/platform-tooling-support-tests/projects/ant-starter/build.xml index 6c805ceb1ad7..79d22fcebc4a 100644 --- a/platform-tooling-support-tests/projects/ant-starter/build.xml +++ b/platform-tooling-support-tests/projects/ant-starter/build.xml @@ -51,6 +51,7 @@ + diff --git a/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java b/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java index c0540f6b3e95..704d536c0866 100644 --- a/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java +++ b/platform-tooling-support-tests/projects/ant-starter/src/main/java/com/example/project/Calculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java index 0b0a25ab7fdc..8f9c8153041d 100644 --- a/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/ant-starter/src/test/java/com/example/project/CalculatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts index c83d5d840ada..6203c65fdcac 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/build.gradle.kts @@ -3,8 +3,9 @@ plugins { id("org.graalvm.buildtools.native") } -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project +val vintageVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -13,11 +14,16 @@ repositories { dependencies { testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testImplementation("junit:junit:4.13.2") + testImplementation("org.junit.platform:junit-platform-suite:$platformVersion") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$vintageVersion") testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") } tasks.test { - useJUnitPlatform() + useJUnitPlatform { + includeEngines("junit-platform-suite") + } val outputDir = reports.junitXml.outputLocation jvmArgumentProviders += CommandLineArgumentProvider { @@ -31,6 +37,7 @@ tasks.test { graalvmNative { binaries { named("test") { + buildArgs.add("--strict-image-heap") buildArgs.add("-H:+ReportExceptionStackTraces") } } diff --git a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts index 1399f18e3201..c6d35e3a0e51 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/graalvm-starter/settings.gradle.kts @@ -1,11 +1,15 @@ pluginManagement { - plugins { - id("org.graalvm.buildtools.native") version "0.10.1" - } - repositories { - mavenCentral() - gradlePluginPortal() - } + plugins { + id("org.graalvm.buildtools.native") version "0.10.5" + } + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" } rootProject.name = "graalvm-starter" diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java b/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java index c0540f6b3e95..704d536c0866 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/main/java/com/example/project/Calculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java index 0b0a25ab7fdc..8f9c8153041d 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/CalculatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/ClassLevelAnnotationTests.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/ClassLevelAnnotationTests.java index 582b46f7cde1..709f4a0de1cb 100644 --- a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/ClassLevelAnnotationTests.java +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/ClassLevelAnnotationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java new file mode 100644 index 000000000000..ad02aea308af --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/GraalvmSuite.java @@ -0,0 +1,18 @@ +/* + * 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 com.example.project; + +import org.junit.platform.suite.api.*; + +@Suite +@SelectPackages("com.example.project") +public class GraalvmSuite { +} diff --git a/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java new file mode 100644 index 000000000000..d12865d1e312 --- /dev/null +++ b/platform-tooling-support-tests/projects/graalvm-starter/src/test/java/com/example/project/VintageTests.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package com.example.project; + +import org.junit.Test; + +public class VintageTests { + @Test + public void test() { + } +} 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 51a6ffd269ad..e813a58e9dc9 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 "1.9.0" + kotlin("jvm") version "2.1.10" } repositories { @@ -9,12 +9,19 @@ repositories { mavenCentral() } -// grab jupiter version from system environment -val jupiterVersion = System.getenv("JUNIT_JUPITER_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project dependencies { testImplementation(kotlin("stdlib-jdk8")) testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$platformVersion") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } tasks.withType().configureEach { diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts index 7a052453781c..d7d6bc0549a6 100644 --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} + rootProject.name = "gradle-kotlin-extensions" diff --git a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt index d60205669fc3..ccc93d4c789b 100644 --- a/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt +++ b/platform-tooling-support-tests/projects/gradle-kotlin-extensions/src/test/kotlin/com/example/project/ExtensionFunctionsTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts index 022b1d3a71eb..50b4e7b2924b 100644 --- a/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/build.gradle.kts @@ -2,25 +2,8 @@ plugins { java } -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") - -// emit default file encoding to a file -file("file.encoding.txt").writeText(System.getProperty("file.encoding")) - -// emit more Java runtime information -file("java.runtime.txt").writeText(""" -java.version=${System.getProperty("java.version")} -""") - -// emit versions of JUnit groups -file("junit.versions.txt").writeText(""" -jupiterVersion=$jupiterVersion -vintageVersion=$vintageVersion -platformVersion=$platformVersion -""") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -31,6 +14,12 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api:$jupiterVersion") { exclude(group = "org.junit.jupiter", module = "junit-jupiter-engine") } + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$platformVersion")} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } tasks.test { diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties b/platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts b/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts index c68450bb3bf5..9eb91b3f491b 100644 --- a/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} + rootProject.name = "gradle-missing-engine" diff --git a/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java b/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java index 97be110987fc..f6c8e9c71b9a 100644 --- a/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java +++ b/platform-tooling-support-tests/projects/gradle-missing-engine/src/test/java/FooTests.java @@ -1,6 +1,6 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,7 +10,6 @@ */ import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class FooTests { diff --git a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts index e1053da535b7..751889da23d0 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/build.gradle.kts @@ -2,10 +2,8 @@ plugins { java } -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -17,11 +15,17 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + tasks.test { useJUnitPlatform() testLogging { - events("passed", "skipped", "failed") + events("passed", "skipped", "failed", "standardOut") } reports { @@ -35,8 +39,4 @@ tasks.test { "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } - - doFirst { - println("Using Java version: ${JavaVersion.current()}") - } } diff --git a/platform-tooling-support-tests/projects/gradle-starter/gradle.properties b/platform-tooling-support-tests/projects/gradle-starter/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/gradle-starter/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts b/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts index 481940859cf9..3a1befd4bfac 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/gradle-starter/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} + rootProject.name = "gradle-starter" diff --git a/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java b/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java index c0540f6b3e95..704d536c0866 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java +++ b/platform-tooling-support-tests/projects/gradle-starter/src/main/java/com/example/project/Calculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java index 0b0a25ab7fdc..d4ab5f97afcc 100644 --- a/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/gradle-starter/src/test/java/com/example/project/CalculatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -19,6 +20,11 @@ class CalculatorTests { + @BeforeAll + static void printJavaVersion() { + System.out.println("Using Java version: " + System.getProperty("java.specification.version")); + } + @Test @DisplayName("1 + 1 = 2") void addsTwoNumbers() { diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt index 0b4810a6cc60..26719afdbe79 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-jupiter-api.expected.txt @@ -2,6 +2,7 @@ org.junit.jupiter.api@${jupiterVersion} jar:file:.+/junit-jupiter-api-\d.+\.jar. exports org.junit.jupiter.api exports org.junit.jupiter.api.condition exports org.junit.jupiter.api.extension +exports org.junit.jupiter.api.extension.support exports org.junit.jupiter.api.function exports org.junit.jupiter.api.io exports org.junit.jupiter.api.parallel diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt index cb3eb72ae947..11e66a7ca44f 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-commons.expected.txt @@ -4,9 +4,11 @@ exports org.junit.platform.commons.annotation exports org.junit.platform.commons.function exports org.junit.platform.commons.support exports org.junit.platform.commons.support.conversion +exports org.junit.platform.commons.support.scanning requires java.base mandated requires java.logging requires java.management requires org.apiguardian.api static transitive +uses org.junit.platform.commons.support.scanning.ClasspathScanner qualified exports org.junit.platform.commons.logging to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine qualified exports org.junit.platform.commons.util to org.junit.jupiter.api org.junit.jupiter.engine org.junit.jupiter.migrationsupport org.junit.jupiter.params org.junit.platform.console org.junit.platform.engine org.junit.platform.launcher org.junit.platform.reporting org.junit.platform.runner org.junit.platform.suite.api org.junit.platform.suite.commons org.junit.platform.suite.engine org.junit.platform.testkit org.junit.vintage.engine diff --git a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt index e9dbd208e00a..2362fbb8152f 100644 --- a/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt +++ b/platform-tooling-support-tests/projects/jar-describe-module/junit-platform-reporting.expected.txt @@ -8,4 +8,6 @@ requires org.apiguardian.api static transitive requires org.junit.platform.commons requires org.junit.platform.engine transitive requires org.junit.platform.launcher transitive +requires org.opentest4j.reporting.tooling.spi provides org.junit.platform.launcher.TestExecutionListener with org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener +provides org.opentest4j.reporting.tooling.spi.htmlreport.Contributor with org.junit.platform.reporting.open.xml.JUnitContributor diff --git a/platform-tooling-support-tests/projects/java-versions/pom.xml b/platform-tooling-support-tests/projects/java-versions/pom.xml index 7123e267d45c..d9d22bbec6b6 100644 --- a/platform-tooling-support-tests/projects/java-versions/pom.xml +++ b/platform-tooling-support-tests/projects/java-versions/pom.xml @@ -9,8 +9,6 @@ UTF-8 - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_PLATFORM_VERSION} @@ -33,7 +31,7 @@ maven-compiler-plugin - 3.8.1 + 3.13.0 1.8 1.8 @@ -41,7 +39,7 @@ maven-surefire-plugin - 2.22.2 + 3.5.2 @@ -53,9 +51,11 @@ file://${maven.repo} true + ignore true + ignore diff --git a/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java b/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java index 1d76fa7b2ed4..7e7a888bdd6b 100644 --- a/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java +++ b/platform-tooling-support-tests/projects/java-versions/src/test/java/JUnitPlatformCommonsTests.java @@ -1,6 +1,6 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/maven-starter/pom.xml b/platform-tooling-support-tests/projects/maven-starter/pom.xml index 18b577f81a4e..c03e69a3277c 100644 --- a/platform-tooling-support-tests/projects/maven-starter/pom.xml +++ b/platform-tooling-support-tests/projects/maven-starter/pom.xml @@ -11,10 +11,16 @@ UTF-8 1.8 ${maven.compiler.source} - ${env.JUNIT_JUPITER_VERSION} + ${junit.platform.version} + + org.junit.platform + junit-platform-commons + ${junit.platform.commons.version} + test + org.junit.jupiter junit-jupiter @@ -43,11 +49,11 @@ maven-compiler-plugin - 3.8.1 + 3.13.0 maven-surefire-plugin - 2.22.2 + 3.5.2 @@ -84,9 +90,21 @@ file://${maven.repo} true + ignore true + ignore + + + + snapshots-repo + ${snapshot.repo.url} + + true + + + false diff --git a/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java b/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java index c0540f6b3e95..704d536c0866 100644 --- a/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java +++ b/platform-tooling-support-tests/projects/maven-starter/src/main/java/com/example/project/Calculator.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java b/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java index 0b0a25ab7fdc..8f9c8153041d 100644 --- a/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java +++ b/platform-tooling-support-tests/projects/maven-starter/src/test/java/com/example/project/CalculatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml b/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml index 58f907ab1e05..3ac0876b2d8b 100644 --- a/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml +++ b/platform-tooling-support-tests/projects/maven-surefire-compatibility/pom.xml @@ -11,7 +11,6 @@ UTF-8 1.8 ${maven.compiler.source} - ${env.JUNIT_JUPITER_VERSION} @@ -38,7 +37,7 @@ maven-compiler-plugin - 3.8.1 + 3.13.0 maven-surefire-plugin @@ -60,9 +59,11 @@ file://${maven.repo} true + ignore true + ignore diff --git a/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java b/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java index b9f8a24f46b0..8ce5c082ef86 100644 --- a/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java +++ b/platform-tooling-support-tests/projects/maven-surefire-compatibility/src/test/java/com/example/project/DummyTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,12 +10,7 @@ package com.example.project; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; class DummyTests { diff --git a/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml b/platform-tooling-support-tests/projects/multi-release-jar/pom.xml similarity index 81% rename from platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml rename to platform-tooling-support-tests/projects/multi-release-jar/pom.xml index 0904feb98643..9c96f86949bf 100644 --- a/platform-tooling-support-tests/projects/multi-release-jar/default/pom.xml +++ b/platform-tooling-support-tests/projects/multi-release-jar/pom.xml @@ -9,9 +9,6 @@ UTF-8 - ${env.JUNIT_JUPITER_VERSION} - ${env.JUNIT_VINTAGE_VERSION} - ${env.JUNIT_PLATFORM_VERSION} @@ -34,7 +31,7 @@ maven-compiler-plugin - 3.8.1 + 3.13.0 11 @@ -45,7 +42,7 @@ de.sormuras.junit junit-platform-maven-plugin - 1.1.5 + 1.1.8 true JAVA @@ -63,9 +60,21 @@ file://${maven.repo} true + ignore true + ignore + + + + snapshots-repo + ${snapshot.repo.url} + + true + + + false diff --git a/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/JupiterIntegrationTests.java similarity index 86% rename from platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java rename to platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/JupiterIntegrationTests.java index d3664cae5998..1ec807b3a8dc 100644 --- a/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/JupiterIntegrationTests.java +++ b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/JupiterIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,7 +11,6 @@ package integration; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.junit.platform.launcher.EngineFilter.includeEngines; import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; @@ -20,9 +19,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.platform.engine.discovery.DiscoverySelectors; -import org.junit.platform.engine.discovery.ModuleSelector; -import org.junit.platform.launcher.TestIdentifier; -import org.junit.platform.launcher.TestPlan; import org.junit.platform.launcher.core.LauncherFactory; @TestMethodOrder(Alphanumeric.class) diff --git a/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java similarity index 94% rename from platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java rename to platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java index d4d961dcc251..858a8d332111 100644 --- a/platform-tooling-support-tests/projects/multi-release-jar/default/src/test/java/integration/integration/ModuleUtilsTests.java +++ b/platform-tooling-support-tests/projects/multi-release-jar/src/test/java/integration/integration/ModuleUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.platform.commons.PreconditionViolationException; -import org.junit.platform.commons.util.ClassFilter; +import org.junit.platform.commons.support.scanning.ClassFilter; import org.junit.platform.commons.util.ModuleUtils; /** diff --git a/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts b/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts index 653257ae1042..0dea5aab80a1 100644 --- a/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts +++ b/platform-tooling-support-tests/projects/reflection-tests/build.gradle.kts @@ -2,10 +2,8 @@ plugins { java } -// grab jupiter version from system environment -val jupiterVersion: String = System.getenv("JUNIT_JUPITER_VERSION") -val vintageVersion: String = System.getenv("JUNIT_VINTAGE_VERSION") -val platformVersion: String = System.getenv("JUNIT_PLATFORM_VERSION") +val jupiterVersion: String by project +val platformVersion: String by project repositories { maven { url = uri(file(System.getProperty("maven.repo"))) } @@ -18,11 +16,17 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion") } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + tasks.test { useJUnitPlatform() testLogging { - events("failed") + events("failed", "standardOut") } reports { @@ -36,8 +40,4 @@ tasks.test { "-Djunit.platform.reporting.output.dir=${outputDir.get().asFile.absolutePath}" ) } - - doFirst { - println("Using Java version: ${JavaVersion.current()}") - } } diff --git a/platform-tooling-support-tests/projects/reflection-tests/gradle.properties b/platform-tooling-support-tests/projects/reflection-tests/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/reflection-tests/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts b/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts index af17e8f41649..7ec746923227 100644 --- a/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/reflection-tests/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} + rootProject.name = "reflection-tests" diff --git a/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java b/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java index 0692db43426b..188ff6eaf620 100644 --- a/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java +++ b/platform-tooling-support-tests/projects/reflection-tests/src/test/java/ReflectionTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor; @@ -31,6 +32,11 @@ class ReflectionTestCase { + @BeforeAll + static void printJavaVersion() { + System.out.println("Using Java version: " + System.getProperty("java.specification.version")); + } + @TestFactory Stream canReadParameters() { return Stream.of(JupiterTestDescriptor.class, ClassBasedTestDescriptor.class, ClassTestDescriptor.class, diff --git a/platform-tooling-support-tests/projects/standalone/expected-err.txt b/platform-tooling-support-tests/projects/standalone/expected-err.txt index de1899fd021a..53ca33b7f6f0 100644 --- a/platform-tooling-support-tests/projects/standalone/expected-err.txt +++ b/platform-tooling-support-tests/projects/standalone/expected-err.txt @@ -12,7 +12,7 @@ .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load .+ Loaded LauncherDiscoveryListener instances: .. .+ org.junit.platform.launcher.core.ServiceLoaderRegistry load -.+ Loaded TestExecutionListener instances: .+ +.+ Loaded TestExecutionListener instances: .+ \Q(excluded classes: [])\E .+ org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry loadTestEngines .+ Discovered TestEngines: - junit-platform-suite .+ diff --git a/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java b/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java index 718927809b9c..e8bbce2489cb 100644 --- a/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java +++ b/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java b/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java index c2e632a83a1e..7d81da33f214 100644 --- a/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java +++ b/platform-tooling-support-tests/projects/standalone/src/standalone/JupiterParamsIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java b/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java index 3719ebd097af..5210add0a831 100644 --- a/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java +++ b/platform-tooling-support-tests/projects/standalone/src/standalone/SuiteIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java b/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java index 151460a6f0cc..a234e3516450 100644 --- a/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java +++ b/platform-tooling-support-tests/projects/standalone/src/standalone/VintageIntegration.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/projects/vintage/build.gradle.kts b/platform-tooling-support-tests/projects/vintage/build.gradle.kts index 4d691459831f..37bab7448d5b 100644 --- a/platform-tooling-support-tests/projects/vintage/build.gradle.kts +++ b/platform-tooling-support-tests/projects/vintage/build.gradle.kts @@ -1,4 +1,3 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent plugins { @@ -10,15 +9,25 @@ repositories { mavenCentral() } +val platformVersion: String by project +val vintageVersion: String by project + dependencies { val junit4Version = System.getProperty("junit4Version", "4.12") testImplementation("junit:junit:$junit4Version") - val vintageVersion = System.getenv("JUNIT_VINTAGE_VERSION") ?: "5.3.2" testImplementation("org.junit.vintage:junit-vintage-engine:$vintageVersion") { exclude(group = "junit") because("we want to override it to test against different versions") } + + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$platformVersion") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } } tasks.test { diff --git a/platform-tooling-support-tests/projects/vintage/gradle.properties b/platform-tooling-support-tests/projects/vintage/gradle.properties new file mode 100644 index 000000000000..79b428ebc70c --- /dev/null +++ b/platform-tooling-support-tests/projects/vintage/gradle.properties @@ -0,0 +1 @@ +org.gradle.java.installations.fromEnv=JDK8 diff --git a/platform-tooling-support-tests/projects/vintage/pom.xml b/platform-tooling-support-tests/projects/vintage/pom.xml index 47a0f145cf89..164570994740 100644 --- a/platform-tooling-support-tests/projects/vintage/pom.xml +++ b/platform-tooling-support-tests/projects/vintage/pom.xml @@ -11,14 +11,13 @@ UTF-8 1.8 ${maven.compiler.source} - ${env.JUNIT_VINTAGE_VERSION} org.junit.vintage junit-vintage-engine - ${vintageVersion} + ${junit.vintage.version} test @@ -39,11 +38,11 @@ maven-compiler-plugin - 3.8.1 + 3.13.0 maven-surefire-plugin - 2.22.2 + 3.5.2 @@ -54,9 +53,11 @@ file://${maven.repo} true + ignore true + ignore diff --git a/platform-tooling-support-tests/projects/vintage/settings.gradle.kts b/platform-tooling-support-tests/projects/vintage/settings.gradle.kts index f073959d096e..a890cc361b07 100644 --- a/platform-tooling-support-tests/projects/vintage/settings.gradle.kts +++ b/platform-tooling-support-tests/projects/vintage/settings.gradle.kts @@ -1 +1,5 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0" +} + rootProject.name = "vintage" diff --git a/platform-tooling-support-tests/projects/vintage/src/test/java/DefaultPackageTest.java b/platform-tooling-support-tests/projects/vintage/src/test/java/DefaultPackageTest.java new file mode 100644 index 000000000000..8d73c776b02b --- /dev/null +++ b/platform-tooling-support-tests/projects/vintage/src/test/java/DefaultPackageTest.java @@ -0,0 +1,22 @@ + +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ +import com.example.vintage.VintageTest; + +import org.junit.Ignore; + +/** + * Reproducer for https://github.com/junit-team/junit5/issues/4076 + */ +@Ignore +public class DefaultPackageTest extends VintageTest { + void packagePrivateMethod() { + } +} diff --git a/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java b/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java index 1632b1e3537b..94a1b44247a8 100644 --- a/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java +++ b/platform-tooling-support-tests/projects/vintage/src/test/java/com/example/vintage/VintageTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,9 @@ import org.junit.Test; public class VintageTest { + void packagePrivateMethod() { + } + @Test public void success() { // pass diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java index 4bb2c6c73ad6..7deff0c5ed1c 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Helper.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,23 +10,15 @@ package platform.tooling.support; -import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Properties; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -34,8 +26,6 @@ */ public class Helper { - public static final Duration TOOL_TIMEOUT = Duration.ofMinutes(3); - private static final Path ROOT = Paths.get(".."); private static final Path GRADLE_PROPERTIES = ROOT.resolve("gradle.properties"); private static final Path SETTINGS_GRADLE = ROOT.resolve("settings.gradle.kts"); @@ -64,56 +54,21 @@ public static String version(String module) { throw new AssertionError("Unknown module: " + module); } - static String groupId(String artifactId) { - if (artifactId.startsWith("junit-jupiter")) { - return "org.junit.jupiter"; - } - if (artifactId.startsWith("junit-platform")) { - return "org.junit.platform"; - } - if (artifactId.startsWith("junit-vintage")) { - return "org.junit.vintage"; - } - return "org.junit"; - } - - public static String replaceVersionPlaceholders(String line) { - line = line.replace("${jupiterVersion}", version("junit-jupiter")); - line = line.replace("${vintageVersion}", version("junit-vintage")); - line = line.replace("${platformVersion}", version("junit-platform")); - return line; - } - public static List loadModuleDirectoryNames() { var moduleLinePattern = Pattern.compile("include\\(\"(.+)\"\\)"); - try (var stream = Files.lines(SETTINGS_GRADLE) // - .map(moduleLinePattern::matcher) // - .filter(Matcher::matches) // - .map(matcher -> matcher.group(1)) // - .filter(name -> name.startsWith("junit-")) // - .filter(name -> !name.equals("junit-bom")) // - .filter(name -> !name.equals("junit-platform-console-standalone"))) { - return stream.collect(Collectors.toList()); + try (var stream = Files.lines(SETTINGS_GRADLE)) { + return stream.map(moduleLinePattern::matcher) // + .filter(Matcher::matches) // + .map(matcher -> matcher.group(1)) // + .filter(name -> name.startsWith("junit-")) // + .filter(name -> !name.equals("junit-bom")) // + .filter(name -> !name.equals("junit-platform-console-standalone")).toList(); } catch (Exception e) { throw new AssertionError("loading module directory names failed: " + SETTINGS_GRADLE); } } - static JarFile createJarFile(String module) { - var path = MavenRepo.jar(module); - try { - return new JarFile(path.toFile()); - } - catch (IOException e) { - throw new UncheckedIOException("Creating JarFile for '" + path + "' failed.", e); - } - } - - public static List loadJarFiles() { - return loadModuleDirectoryNames().stream().map(Helper::createJarFile).collect(Collectors.toList()); - } - public static Optional getJavaHome(String version) { // First, try various system sources... var sources = Stream.of( // @@ -127,32 +82,4 @@ public static Optional getJavaHome(String version) { ); return sources.filter(Objects::nonNull).findFirst().map(Path::of); } - - /** Load, here copy, modular jar files to the given target directory. */ - public static void loadAllJUnitModules(Path target) throws Exception { - for (var module : loadModuleDirectoryNames()) { - var jar = MavenRepo.jar(module); - Files.copy(jar, target.resolve(jar.getFileName())); - } - } - - /** Walk directory tree structure. */ - public static List treeWalk(Path root) { - var lines = new ArrayList(); - treeWalk(root, lines::add); - return lines; - } - - /** Walk directory tree structure. */ - public static void treeWalk(Path root, Consumer out) { - try (var stream = Files.walk(root)) { - stream.map(root::relativize) // - .map(path -> path.toString().replace('\\', '/')) // - .sorted().filter(Predicate.not(String::isEmpty)) // - .forEach(out); - } - catch (Exception e) { - throw new Error("Walking tree failed: " + root, e); - } - } } diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java index 2ba22ebeb8cc..d5ebac39bc92 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/MavenRepo.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -45,7 +45,7 @@ public static Path pom(String artifactId) { private static Path artifact(String artifactId, Predicate fileNamePredicate) { var parentDir = dir() // - .resolve(Helper.groupId(artifactId).replace('.', File.separatorChar)) // + .resolve(groupId(artifactId).replace('.', File.separatorChar)) // .resolve(artifactId) // .resolve(Helper.version(artifactId)); try (var files = Files.list(parentDir)) { @@ -57,4 +57,16 @@ private static Path artifact(String artifactId, Predicate fileNamePredic } } + private static String groupId(String artifactId) { + if (artifactId.startsWith("junit-jupiter")) { + return "org.junit.jupiter"; + } + if (artifactId.startsWith("junit-platform")) { + return "org.junit.platform"; + } + if (artifactId.startsWith("junit-vintage")) { + return "org.junit.vintage"; + } + return "org.junit"; + } } diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java new file mode 100644 index 000000000000..773b360aed7c --- /dev/null +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ProcessStarters.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support; + +import java.nio.file.Path; +import java.util.Optional; + +import org.junit.jupiter.api.condition.OS; +import org.junit.platform.tests.process.ProcessStarter; +import org.opentest4j.TestAbortedException; + +public class ProcessStarters { + + public static ProcessStarter java() { + return javaCommand(currentJdkHome(), "java"); + } + + public static Path currentJdkHome() { + var executable = ProcessHandle.current().info().command().map(Path::of).orElseThrow(); + // path element count is 3 or higher: "/bin/java[.exe]" + return executable.getParent().getParent().toAbsolutePath(); + } + + public static ProcessStarter java(Path javaHome) { + return javaCommand(javaHome, "java"); + } + + public static ProcessStarter javaCommand(String commandName) { + return javaCommand(currentJdkHome(), commandName); + } + + public static ProcessStarter javaCommand(Path javaHome, String commandName) { + return new ProcessStarter() // + .executable(javaHome.resolve("bin").resolve(commandName)) // + .putEnvironment("JAVA_HOME", javaHome); + } + + public static ProcessStarter gradlew() { + return new ProcessStarter() // + .executable(Path.of("..").resolve(windowsOrOtherExecutable("gradlew.bat", "gradlew")).toAbsolutePath()) // + .putEnvironment("JAVA_HOME", getGradleJavaHome().orElseThrow(TestAbortedException::new)) // + .addArguments("-PjupiterVersion=" + Helper.version("junit-jupiter")) // + .addArguments("-PvintageVersion=" + Helper.version("junit-vintage")) // + .addArguments("-PplatformVersion=" + Helper.version("junit-platform")); + } + + public static ProcessStarter maven() { + return maven(currentJdkHome()); + } + + public static ProcessStarter maven(Path javaHome) { + return new ProcessStarter() // + .executable(Path.of(System.getProperty("mavenDistribution")).resolve("bin").resolve( + windowsOrOtherExecutable("mvn.cmd", "mvn")).toAbsolutePath()) // + .putEnvironment("JAVA_HOME", javaHome) // + .addArguments("-Djunit.jupiter.version=" + Helper.version("junit-jupiter")) // + .addArguments("-Djunit.bom.version=" + Helper.version("junit-jupiter")) // + .addArguments("-Djunit.vintage.version=" + Helper.version("junit-vintage")) // + .addArguments("-Djunit.platform.version=" + Helper.version("junit-platform")); + } + + private static String windowsOrOtherExecutable(String cmdOrExe, String other) { + return OS.current() == OS.WINDOWS ? cmdOrExe : other; + } + + public static Optional getGradleJavaHome() { + return Helper.getJavaHome(System.getProperty("gradle.java.version")); + } +} diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java deleted file mode 100644 index 3ccaac91edee..000000000000 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/Request.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support; - -import java.io.FileFilter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -import de.sormuras.bartholdy.Configuration; -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.Tool; -import de.sormuras.bartholdy.tool.Maven; - -import org.apache.commons.io.FileUtils; - -/** - * @since 1.3 - */ -public class Request { - - public static final Path PROJECTS = Paths.get("projects"); - private static final Path TOOLS = Paths.get("build", "test-tools"); - public static final Path WORKSPACE = Paths.get("build", "test-workspace"); - - public static Builder builder() { - return new Builder(); - } - - public static Maven maven() { - return new Maven(Path.of(System.getProperty("mavenDistribution"))); - } - - private Tool tool; - private String project; - private String workspace; - private List arguments = new ArrayList<>(); - private Map environment = new HashMap<>(); - private FileFilter copyProjectToWorkspaceFileFilter; - private Duration timeout = Duration.ofMinutes(1); - - public String getProject() { - return project; - } - - public FileFilter getCopyProjectToWorkspaceFileFilter() { - return copyProjectToWorkspaceFileFilter; - } - - public String getWorkspace() { - return workspace; - } - - public Map getEnvironment() { - return environment; - } - - public List getArguments() { - return arguments; - } - - public Duration getTimeout() { - return timeout; - } - - public Result run() { - return run(true); - } - - public Result run(boolean cleanWorkspace) { - try { - // sanity check - if (!Files.isDirectory(PROJECTS)) { - var cwd = Paths.get(".").normalize().toAbsolutePath(); - throw new IllegalStateException("Directory " + PROJECTS + " not found in: " + cwd); - } - - Files.createDirectories(TOOLS); - Files.createDirectories(WORKSPACE); - - var workspace = WORKSPACE.resolve(getWorkspace()); - if (cleanWorkspace) { - FileUtils.deleteQuietly(workspace.toFile()); - var project = PROJECTS.resolve(getProject()); - if (Files.isDirectory(project)) { - var filter = getCopyProjectToWorkspaceFileFilter(); - FileUtils.copyDirectory(project.toFile(), workspace.toFile(), filter); - } - } - - var configuration = Configuration.builder(); - configuration.setArguments(getArguments()); - configuration.setWorkingDirectory(workspace); - configuration.setTimeout(getTimeout()); - configuration.getEnvironment().putAll(getEnvironment()); - - var result = tool.run(configuration.build()); - System.out.println(result.getOutput("out")); - System.err.println(result.getOutput("err")); - return result; - } - catch (Exception e) { - throw new IllegalStateException("run failed", e); - } - } - - public static class Builder { - - private final Request request = new Request(); - - public Request build() { - if (request.project == null) { - throw new IllegalStateException("project must not be null"); - } - if (request.workspace == null) { - request.workspace = request.project; - } - buildEnvironment(request.environment); - request.arguments = List.copyOf(request.arguments); - request.environment = Map.copyOf(request.environment); - return request; - } - - private void buildEnvironment(Map environment) { - environment.computeIfAbsent("JUNIT_JUPITER_VERSION", key -> Helper.version("junit-jupiter")); - environment.computeIfAbsent("JUNIT_VINTAGE_VERSION", key -> Helper.version("junit-vintage")); - environment.computeIfAbsent("JUNIT_PLATFORM_VERSION", key -> Helper.version("junit-platform")); - } - - public Builder setTool(Tool tool) { - request.tool = tool; - return this; - } - - public Builder setJavaHome(Path javaHome) { - return putEnvironment("JAVA_HOME", javaHome.normalize().toAbsolutePath().toString()); - } - - public Builder setProject(String project) { - request.project = project; - return this; - } - - public Builder setProjectToWorkspaceCopyFileFilter(FileFilter copyProjectToWorkspaceFileFilter) { - request.copyProjectToWorkspaceFileFilter = copyProjectToWorkspaceFileFilter; - return this; - } - - public Builder setWorkspace(String workspace) { - request.workspace = workspace; - return this; - } - - public Builder addArguments(Object... arguments) { - Stream.of(arguments).map(Object::toString).forEach(request.arguments::add); - return this; - } - - public Builder putEnvironment(String key, String value) { - request.environment.put(key, value); - return this; - } - - public Builder setTimeout(Duration timeout) { - request.timeout = timeout; - return this; - } - } -} diff --git a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java index 04ff5084b0dd..7873f4cae08d 100644 --- a/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java +++ b/platform-tooling-support-tests/src/main/java/platform/tooling/support/ThirdPartyJars.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java index 15fe8d5e4ecd..6aaa2b1c29be 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/HelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java index cfcda9130c05..f451fa332b66 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/AntStarterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,41 +11,37 @@ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertLinesMatch; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; +import java.nio.file.Path; import java.util.List; -import de.sormuras.bartholdy.tool.Java; - import org.apache.tools.ant.Main; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class AntStarterTests { - @ResourceLock(Projects.ANT_STARTER) @Test - void ant_starter() { - var request = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.ANT_STARTER) // + @Timeout(60) + void ant_starter(@TempDir Path workspace, @FilePrefix("ant") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.java() // + .workingDir(copyToWorkspace(Projects.ANT_STARTER, workspace)) // .addArguments("-cp", System.getProperty("antJars"), Main.class.getName()) // - .addArguments("-verbose") // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err"), "error log isn't empty"); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr(), "error log isn't empty"); assertLinesMatch(List.of(">> HEAD >>", // "test.junit.launcher:", // ">>>>", // @@ -58,9 +54,9 @@ void ant_starter() { " \\[java\\] \\[ 5 tests successful \\]", // " \\[java\\] \\[ 0 tests failed \\]", // ">> TAIL >>"), // - result.getOutputLines("out")); + result.stdOutLines()); - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-report"); + var testResultsDir = workspace.resolve("build/test-report"); verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java index ff32c0777736..635ea9b5f630 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ArchUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -21,31 +21,51 @@ import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.modifier; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; +import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.loadJarFiles; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Arrays; import java.util.Set; -import java.util.stream.Collectors; +import java.util.function.BiPredicate; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.Location; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.junit.LocationProvider; +import com.tngtech.archunit.lang.ArchCondition; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.library.GeneralCodingRules; import org.apiguardian.api.API; -import org.junit.jupiter.api.Order; -@Order(Integer.MAX_VALUE) +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; + @AnalyzeClasses(locations = ArchUnitTests.AllJars.class) class ArchUnitTests { + @SuppressWarnings("unused") + @ArchTest + private final ArchRule allClassesAreInJUnitPackage = classes() // + .should().haveNameMatching("org\\.junit\\..+"); + + @SuppressWarnings("unused") @ArchTest private final ArchRule allPublicTopLevelTypesHaveApiAnnotations = classes() // .that(have(modifier(PUBLIC))) // @@ -55,6 +75,15 @@ class ArchUnitTests { .and(not(describe("are shadowed", resideInAnyPackage("..shadow..")))) // .should().beAnnotatedWith(API.class); + @SuppressWarnings("unused") + @ArchTest // Consistency of @Documented and @Inherited is checked by the compiler but not for @Retention and @Target + private final ArchRule repeatableAnnotationsShouldHaveMatchingContainerAnnotations = classes() // + .that(nameStartingWith("org.junit.")) // + .and().areAnnotations() // + .and().areAnnotatedWith(Repeatable.class) // + .should(haveContainerAnnotationWithSameRetentionPolicy()) // + .andShould(haveContainerAnnotationWithSameTargetTypes()); + @ArchTest void allAreIn(JavaClasses classes) { // about 928 classes found in all jars @@ -90,17 +119,65 @@ void avoidAccessingStandardStreams(JavaClasses classes) { .that(are(not(name("org.junit.platform.runner.JUnitPlatformRunnerListener")))) // .that(are(not(name("org.junit.platform.testkit.engine.Events")))) // .that(are(not(name("org.junit.platform.testkit.engine.Executions")))) // + //The PreInterruptThreadDumpPrinter writes to StdOut by contract to dump threads + .that(are(not(name("org.junit.jupiter.engine.extension.PreInterruptThreadDumpPrinter")))) // .that(are(not(resideInAPackage("org.junit.platform.console.shadow.picocli")))); GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(subset); } + private static ArchCondition haveContainerAnnotationWithSameRetentionPolicy() { + return ArchCondition.from(new RepeatableAnnotationPredicate<>(Retention.class, + (expectedTarget, actualTarget) -> expectedTarget.value() == actualTarget.value())); + } + + private static ArchCondition haveContainerAnnotationWithSameTargetTypes() { + return ArchCondition.from(new RepeatableAnnotationPredicate<>(Target.class, + (expectedTarget, actualTarget) -> Arrays.equals(expectedTarget.value(), actualTarget.value()))); + } + static class AllJars implements LocationProvider { @Override public Set get(Class testClass) { - return loadJarFiles().stream().map(Location::of).collect(Collectors.toSet()); + return loadJarFiles().map(Location::of).collect(toSet()); + } + + private static Stream loadJarFiles() { + return Helper.loadModuleDirectoryNames().stream().map(AllJars::createJarFile); } + private static JarFile createJarFile(String module) { + var path = MavenRepo.jar(module); + try { + return new JarFile(path.toFile()); + } + catch (IOException e) { + throw new UncheckedIOException("Creating JarFile for '" + path + "' failed.", e); + } + } } + private static class RepeatableAnnotationPredicate extends DescribedPredicate { + + private final Class annotationType; + private final BiPredicate predicate; + + public RepeatableAnnotationPredicate(Class annotationType, BiPredicate predicate) { + super("have identical @%s annotation as container annotation", annotationType.getSimpleName()); + this.annotationType = annotationType; + this.predicate = predicate; + } + + @Override + public boolean test(JavaClass annotationClass) { + var containerAnnotationClass = (JavaClass) annotationClass.getAnnotationOfType( + Repeatable.class.getName()).get("value").orElseThrow(); + var expectedAnnotation = annotationClass.tryGetAnnotationOfType(annotationType); + var actualAnnotation = containerAnnotationClass.tryGetAnnotationOfType(annotationType); + return expectedAnnotation.map(expectedTarget -> actualAnnotation // + .map(actualTarget -> predicate.test(expectedTarget, actualTarget)) // + .orElse(false)) // + .orElse(actualAnnotation.isEmpty()); + } + } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/FilePrefix.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/FilePrefix.java new file mode 100644 index 000000000000..a1bc3185986d --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/FilePrefix.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(OutputAttachingExtension.class) +@interface FilePrefix { + String value(); +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java index 349d5d520b4d..092c48967b09 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GraalVmStarterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,23 +10,23 @@ package platform.tooling.support.tests; +import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; -import java.time.Duration; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.9.1 @@ -36,26 +36,24 @@ @EnabledIfEnvironmentVariable(named = "GRAALVM_HOME", matches = ".+") class GraalVmStarterTests { - @ResourceLock(Projects.GRAALVM_STARTER) @Test - void runsTestsInNativeImage() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.GRAALVM_STARTER) // + @Timeout(value = 10, unit = MINUTES) + void runsTestsInNativeImage(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) + throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.GRAALVM_STARTER, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace", "--no-build-cache") // - .setTimeout(Duration.ofMinutes(10)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("javaToolchains", "nativeTest", "--no-daemon", "--stacktrace", "--no-build-cache", + "--warning-mode=fail") // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertThat(result.getOutputLines("out")) // + assertEquals(0, result.exitCode()); + assertThat(result.stdOutLines()) // .anyMatch(line -> line.contains("CalculatorTests > 1 + 1 = 2 SUCCESSFUL")) // .anyMatch(line -> line.contains("CalculatorTests > 1 + 100 = 101 SUCCESSFUL")) // .anyMatch(line -> line.contains("ClassLevelAnnotationTests$Inner > test() SUCCESSFUL")) // + .anyMatch(line -> line.contains("com.example.project.VintageTests > test SUCCESSFUL")) // .anyMatch(line -> line.contains("BUILD SUCCESSFUL")); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java index fee5e16082d0..6c05aef99240 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleKotlinExtensionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,43 +11,36 @@ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleKotlinExtensionsTests { - @ResourceLock(Projects.GRADLE_KOTLIN_EXTENSIONS) @Test - void gradle_wrapper() { - var result = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.GRADLE_KOTLIN_EXTENSIONS) // + void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.GRADLE_KOTLIN_EXTENSIONS, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode(), "result=" + result); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertEquals(0, result.exitCode(), "result=" + result); + assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java index 69204b5a2a46..fdc04d46445e 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleMissingEngineTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,58 +10,42 @@ package platform.tooling.support.tests; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertLinesMatch; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; -import java.util.List; - -import de.sormuras.bartholdy.Tool; -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.reporting.testutil.FileUtils; +import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleMissingEngineTests { - @ResourceLock(Projects.GRADLE_MISSING_ENGINE) @Test - void gradle_wrapper() { - test(new GradleWrapper(Paths.get(".."))); - } - - private void test(Tool gradle) { - var result = Request.builder() // - .setProject(Projects.GRADLE_MISSING_ENGINE) // - .setTool(gradle) // + void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.GRADLE_MISSING_ENGINE, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--debug", "--stacktrace", "--no-build-cache") // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setTimeout(TOOL_TIMEOUT).build() // - .run(); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .redirectOutput(outputFiles).startAndWait(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + assertEquals(1, result.exitCode()); + assertThat(result.stdErrLines()) // + .contains("FAILURE: Build failed with an exception."); - assertEquals(1, result.getExitCode()); - assertLinesMatch(List.of( // - ">> HEAD >>", // - ".+DEBUG.+Cannot create Launcher without at least one TestEngine.+", // - ">> TAIL >>"), // - result.getOutputLines("out")); - assertLinesMatch(List.of( // - ">> HEAD >>", // - ".+ERROR.+FAILURE: Build failed with an exception.", // - ">> TAIL >>"), // - result.getOutputLines("err")); + var htmlFile = FileUtils.findPath(workspace, "glob:**/build/reports/tests/test/classes/*.html"); + assertThat(htmlFile).content() // + .contains("Cannot create Launcher without at least one TestEngine"); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java index e3fd94a7f67b..c79c2b4e54f4 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleModuleFileTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java index 38c400ed79e8..95d0a14a23f2 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/GradleStarterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,49 +12,41 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class GradleStarterTests { - @ResourceLock(Projects.GRADLE_STARTER) @Test - void gradle_wrapper() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.GRADLE_STARTER) // + void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.GRADLE_STARTER, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + assertEquals(0, result.exitCode()); + assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("build/test-results/test"); + var testResultsDir = workspace.resolve("build/test-results/test"); verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java index aac342d5d92f..1a8a30c26adf 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarContainsManifestFirstTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java index a38c353cda4d..4a03dcd5270f 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JarDescribeModuleTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,27 +11,21 @@ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static platform.tooling.support.tests.Projects.getSourceDirectory; import java.lang.module.ModuleFinder; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Collectors; - -import de.sormuras.bartholdy.jdk.Jar; import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 @@ -39,32 +33,24 @@ @Order(Integer.MAX_VALUE) class JarDescribeModuleTests { - @ResourceLock(Projects.JAR_DESCRIBE_MODULE) @ParameterizedTest @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - void describeModule(String module) throws Exception { + void describeModule(String module, @FilePrefix("jar") OutputFiles outputFiles) throws Exception { + var sourceDirectory = getSourceDirectory(Projects.JAR_DESCRIBE_MODULE); var modulePath = MavenRepo.jar(module); - var result = Request.builder() // - .setTool(new Jar()) // - .setProject(Projects.JAR_DESCRIBE_MODULE) // - .setProjectToWorkspaceCopyFileFilter(file -> file.getName().startsWith(module)) // - .setWorkspace("jar-describe-module/" + module) // - .addArguments("--describe-module", "--file", modulePath) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + var result = ProcessStarters.javaCommand("jar") // + .workingDir(sourceDirectory) // + .addArguments("--describe-module", "--file", modulePath.toAbsolutePath().toString()) // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err"), "error log isn't empty"); - var expected = Paths.get("build", "test-workspace", "jar-describe-module", module, module + ".expected.txt"); - if (Files.notExists(expected)) { - result.getOutputLines("out").forEach(System.err::println); - fail("No such file: " + expected); - } - var expectedLines = Files.lines(expected).map(Helper::replaceVersionPlaceholders).collect(Collectors.toList()); - var origin = Path.of("projects", "jar-describe-module", module + ".expected.txt").toUri(); - assertLinesMatch(expectedLines, result.getOutputLines("out"), () -> String.format("%s\nError", origin)); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr(), "error log isn't empty"); + + var expectedLines = replaceVersionPlaceholders( + Files.readString(sourceDirectory.resolve(module + ".expected.txt")).trim()); + assertLinesMatch(expectedLines.lines().toList(), result.stdOut().trim().lines().toList()); } @ParameterizedTest @@ -78,4 +64,11 @@ void packageNamesStartWithNameOfTheModule(String module) { } } + private static String replaceVersionPlaceholders(String line) { + line = line.replace("${jupiterVersion}", Helper.version("junit-jupiter")); + line = line.replace("${vintageVersion}", Helper.version("junit-vintage")); + line = line.replace("${platformVersion}", Helper.version("junit-platform")); + return line; + } + } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java index 54702715df5f..5c35fa41b844 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/JavaVersionsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,57 +11,62 @@ package platform.tooling.support.tests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.ProcessStarters.currentJdkHome; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Path; import java.util.List; - -import de.sormuras.bartholdy.tool.Java; +import java.util.Map; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.4 */ class JavaVersionsTests { + @ManagedResource + LocalMavenRepo localMavenRepo; + + @TempDir + Path workspace; + @Test - void java_8() { + void java_8(@FilePrefix("maven") OutputFiles outputFiles) throws Exception { var java8Home = Helper.getJavaHome("8"); assumeTrue(java8Home.isPresent(), "Java 8 installation directory not found!"); - var actualLines = execute("8", java8Home.get()); + var actualLines = execute(java8Home.get(), outputFiles, Map.of()); assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); } @Test - void java_default() { - var actualLines = execute("default", new Java().getHome()); + void java_default(@FilePrefix("maven") OutputFiles outputFiles) throws Exception { + var actualLines = execute(currentJdkHome(), outputFiles, MavenEnvVars.forJre(JRE.currentJre())); assertTrue(actualLines.contains("[WARNING] Tests run: 2, Failures: 0, Errors: 0, Skipped: 1")); } - List execute(String version, Path javaHome) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.JAVA_VERSIONS) // - .setWorkspace("java-versions-" + version) // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + List execute(Path javaHome, OutputFiles outputFiles, Map environmentVars) throws Exception { + var result = ProcessStarters.maven(javaHome) // + .workingDir(copyToWorkspace(Projects.JAVA_VERSIONS, workspace)) // + .putEnvironment(environmentVars) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("--update-snapshots", "--batch-mode", "verify") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(javaHome) // - .build().run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - return result.getOutputLines("out"); + .startAndWait(); + + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); + return result.stdOutLines(); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java new file mode 100644 index 000000000000..cb3eb217fa75 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/LocalMavenRepo.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static platform.tooling.support.tests.ManagedResource.Scope.GLOBAL; +import static platform.tooling.support.tests.ManagedResource.Scope.PER_CONTEXT; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; + +@ManagedResource.Scoped(LocalMavenRepo.ScopeProvider.class) +public class LocalMavenRepo implements AutoCloseable { + + public static class ScopeProvider implements ManagedResource.Scoped.Provider { + + private static final Namespace NAMESPACE = Namespace.create(LocalMavenRepo.class); + + @Override + public ManagedResource.Scope determineScope(ExtensionContext extensionContext) { + var store = extensionContext.getRoot().getStore(NAMESPACE); + var fileSystemType = store.getOrComputeIfAbsent("tempFileSystemType", key -> { + var type = getFileSystemType(Path.of(System.getProperty("java.io.tmpdir"))); + extensionContext.getRoot().publishReportEntry("tempFileSystemType", type); + return type; + }, String.class); + // Writing to the same file from multiple Maven processes may fail the build on Windows + return "NTFS".equalsIgnoreCase(fileSystemType) ? PER_CONTEXT : GLOBAL; + } + + private static String getFileSystemType(Path path) { + try { + return Files.getFileStore(path).type(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + private final Path tempDir; + + public LocalMavenRepo() { + try { + tempDir = Files.createTempDirectory("local-maven-repo-"); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String toCliArgument() { + return "-Dmaven.repo.local=" + tempDir; + } + + @Override + public void close() throws IOException { + try (var files = Files.walk(tempDir)) { + files.sorted(Comparator. naturalOrder().reversed()) // + .forEach(path -> { + try { + Files.delete(path); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java new file mode 100644 index 000000000000..c278f0a8c0e8 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManagedResource.java @@ -0,0 +1,122 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.platform.commons.support.ReflectionSupport.streamFields; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.Preconditions; + +@Target({ ElementType.PARAMETER, ElementType.FIELD }) +@Retention(RUNTIME) +@ExtendWith(ManagedResource.Extension.class) +public @interface ManagedResource { + + @Target(ElementType.TYPE) + @Retention(RUNTIME) + @interface Scoped { + + Class value(); + + interface Provider { + Scope determineScope(ExtensionContext extensionContext); + } + } + + enum Scope { + GLOBAL, PER_CONTEXT + } + + class Extension implements ParameterResolver, TestInstancePostProcessor { + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.isAnnotated(ManagedResource.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + Class type = parameterContext.getParameter().getType(); + return getOrCreateResource(extensionContext, type).get(); + } + + @Override + public ExtensionContextScope getTestInstantiationExtensionContextScope(ExtensionContext rootContext) { + return ExtensionContextScope.TEST_METHOD; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext) { + streamFields(testInstance.getClass(), field -> AnnotationSupport.isAnnotated(field, ManagedResource.class), + HierarchyTraversalMode.BOTTOM_UP) // + .forEach(field -> { + try { + field.set(testInstance, getOrCreateResource(extensionContext, field.getType()).get()); + } + catch (IllegalAccessException e) { + throw new RuntimeException("Failed to inject resource into field: " + field, e); + } + }); + } + + @SuppressWarnings("unchecked") + private Resource getOrCreateResource(ExtensionContext extensionContext, Class type) { + var scope = AnnotationSupport.findAnnotation(type, Scoped.class) // + .map(Scoped::value) // + .map(ReflectionSupport::newInstance) // + .map(provider -> provider.determineScope(extensionContext)) // + .orElse(Scope.GLOBAL); + var storingContext = switch (scope) { + case GLOBAL -> extensionContext.getRoot(); + case PER_CONTEXT -> extensionContext; + }; + return storingContext.getStore(Namespace.GLOBAL) // + .getOrComputeIfAbsent(type, Resource::new, Resource.class); + } + } + + class Resource implements CloseableResource { + + private final T value; + + private Resource(Class type) { + Preconditions.condition(AutoCloseable.class.isAssignableFrom(type), + () -> "Resource type must implement AutoCloseable: " + type.getName()); + this.value = ReflectionSupport.newInstance(type); + } + + private T get() { + return value; + } + + @Override + public void close() throws Throwable { + ((AutoCloseable) value).close(); + } + } +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java index 5f507ea1d1f9..d215ca8e99c0 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ManifestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenEnvVars.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenEnvVars.java new file mode 100644 index 000000000000..115ea298e815 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenEnvVars.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import java.util.Map; + +import org.junit.jupiter.api.condition.JRE; + +final class MavenEnvVars { + + private static final Map FOR_JDK24_AND_LATER = Map.of("MAVEN_OPTS", String.join(" ", // + "--enable-native-access=ALL-UNNAMED", // https://issues.apache.org/jira/browse/MNG-8248 + "--sun-misc-unsafe-memory-access=allow" // https://issues.apache.org/jira/browse/MNG-8399 + )); + + static Map forJre(JRE jre) { + return jre.compareTo(JRE.JAVA_24) >= 0 ? FOR_JDK24_AND_LATER : Map.of(); + } + + private MavenEnvVars() { + } +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java index e7e4fd4881b8..59dcef5d94ab 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenPomFileTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 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 new file mode 100644 index 000000000000..96a657d750c8 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenRepoProxy.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.List; + +import com.sun.net.httpserver.HttpServer; + +public class MavenRepoProxy implements AutoCloseable { + + // Forbid downloading JUnit artifacts since we want to use the local ones + private static final List FORBIDDEN_PATHS = List.of("/org/junit"); + + private final HttpServer httpServer; + + @SuppressWarnings("unused") + public MavenRepoProxy() throws IOException { + this(0); + } + + @SuppressWarnings("unused") + public MavenRepoProxy(int port) throws IOException { + this("https://oss.sonatype.org/content/repositories/snapshots", port); + } + + private MavenRepoProxy(String proxiedUrl, int port) throws IOException { + httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), 0); + httpServer.createContext("/", exchange -> { + try (exchange) { + switch (exchange.getRequestMethod()) { + case "HEAD": + case "GET": + if (FORBIDDEN_PATHS.stream().anyMatch( + it -> exchange.getRequestURI().getPath().startsWith(it))) { + exchange.sendResponseHeaders(404, -1); + break; + } + var redirectUrl = proxiedUrl + exchange.getRequestURI().getPath(); + exchange.getResponseHeaders().add("Location", redirectUrl); + exchange.sendResponseHeaders(302, -1); + break; + default: + exchange.sendResponseHeaders(405, -1); + } + } + catch (Exception e) { + e.printStackTrace(); + } + }); + httpServer.start(); + } + + URI getBaseUri() { + var address = httpServer.getAddress(); + return URI.create("http://" + address.getAddress().getHostName() + ":" + address.getPort()); + } + + @Override + public void close() { + httpServer.stop(0); + } + +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java index bf5b8a581437..5a795087abae 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenStarterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,47 +12,50 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import static platform.tooling.support.tests.XmlAssertions.verifyContainsExpectedStartedOpenTestReport; +import java.nio.file.Path; + import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.3 */ class MavenStarterTests { - @ResourceLock(Projects.MAVEN_STARTER) - @Test - void verifyMavenStarterProject() { - var request = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.MAVEN_STARTER) // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("--update-snapshots", "--batch-mode", "verify") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); + @ManagedResource + LocalMavenRepo localMavenRepo; - var result = request.run(); + @ManagedResource + MavenRepoProxy mavenRepoProxy; - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + @Test + void verifyMavenStarterProject(@TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles) + throws Exception { + var result = ProcessStarters.maven(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .workingDir(copyToWorkspace(Projects.MAVEN_STARTER, workspace)) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // + .addArguments("--update-snapshots", "--batch-mode", "verify") // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); - assertTrue(result.getOutputLines("out").contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); + assertTrue(result.stdOutLines().contains("[INFO] BUILD SUCCESS")); + assertTrue(result.stdOutLines().contains("[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0")); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); - var testResultsDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target/surefire-reports"); + var testResultsDir = workspace.resolve("target/surefire-reports"); verifyContainsExpectedStartedOpenTestReport(testResultsDir); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java index d0e20e1c450e..3a62853d52aa 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MavenSurefireCompatibilityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,58 +12,55 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.9.2 */ class MavenSurefireCompatibilityTests { - @ResourceLock(Projects.MAVEN_SUREFIRE_COMPATIBILITY) + @ManagedResource + LocalMavenRepo localMavenRepo; + @ParameterizedTest @CsvSource(delimiter = '|', nullValues = "", textBlock = """ 2.22.2 | --activate-profiles=manual-platform-dependency 3.0.0-M4 | """) - void testMavenSurefireCompatibilityProject(String surefireVersion, String extraArg) throws IOException { - var extraArgs = extraArg == null ? new Object[0] : new Object[] { extraArg }; - var request = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.MAVEN_SUREFIRE_COMPATIBILITY) // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + void testMavenSurefireCompatibilityProject(String surefireVersion, String extraArg, @TempDir Path workspace, + @FilePrefix("maven") OutputFiles outputFiles) throws Exception { + var extraArgs = extraArg == null ? new String[0] : new String[] { extraArg }; + var result = ProcessStarters.maven(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .workingDir(copyToWorkspace(Projects.MAVEN_SUREFIRE_COMPATIBILITY, workspace)) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Dsurefire.version=" + surefireVersion) // .addArguments("--update-snapshots", "--batch-mode", "test") // .addArguments(extraArgs) // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); - var output = result.getOutputLines("out"); + var output = result.stdOutLines(); assertTrue(output.contains("[INFO] BUILD SUCCESS")); assertTrue(output.contains("[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0")); - var targetDir = Request.WORKSPACE.resolve(request.getWorkspace()).resolve("target"); + var targetDir = workspace.resolve("target"); try (var stream = Files.list(targetDir)) { assertThat(stream.filter(file -> file.getFileName().toString().startsWith("junit-platform-unique-ids"))) // .hasSize(1); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java index 2427bc41a08a..e7987317cdd2 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ModularUserGuideTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static platform.tooling.support.Helper.loadModuleDirectoryNames; import java.io.File; import java.io.PrintWriter; @@ -23,14 +23,18 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.spi.ToolProvider; -import org.codehaus.groovy.runtime.ProcessGroovyMethods; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.launcher.LauncherConstants; +import org.junit.platform.tests.process.OutputFiles; -import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** @@ -86,9 +90,10 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); ThirdPartyJars.copy(lib, "org.hamcrest", "hamcrest"); ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); + ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); ThirdPartyJars.copy(lib, "com.google.jimfs", "jimfs"); ThirdPartyJars.copy(lib, "com.google.guava", "guava"); - Helper.loadAllJUnitModules(lib); + loadAllJUnitModules(lib); args.add("--module-path"); args.add(lib.toString()); @@ -117,64 +122,38 @@ private static List compile(Path temp, Writer out, Writer err) throws Ex return args; } - private static void junit(Path temp, Writer out, Writer err) throws Exception { - var command = new ArrayList(); - var projectDir = Path.of("../documentation"); - command.add(Path.of(System.getProperty("java.home"), "bin", "java").toString()); - - command.add("-XX:StartFlightRecording:filename=" + temp.resolve("user-guide.jfr")); - - command.add("--show-version"); - command.add("--show-module-resolution"); - - command.add("--module-path"); - command.add(String.join(File.pathSeparator, // - temp.resolve("destination").toString(), // - temp.resolve("lib").toString() // - )); - - command.add("--add-modules"); - command.add("documentation"); - - // TODO This `patch-module` should work! Why doesn't it? - // command.add("--patch-module"); - // command.add("documentation=../documentation/src/test/resources/"); - Files.copy(projectDir.resolve("src/test/resources/two-column.csv"), - temp.resolve("destination/documentation/two-column.csv")); - - command.add("--module"); - command.add("org.junit.platform.console"); - - command.add("--scan-modules"); - - command.add("--config"); - command.add("enableHttpServer=true"); - - command.add("--fail-if-no-tests"); - command.add("--include-classname"); - command.add(".*Tests"); - command.add("--include-classname"); - command.add(".*Demo"); - command.add("--exclude-tag"); - command.add("exclude"); - - // System.out.println("______________"); - // command.forEach(System.out::println); - - var builder = new ProcessBuilder(command).directory(projectDir.toFile()); - var java = builder.start(); - ProcessGroovyMethods.waitForProcessOutput(java, out, err); - var code = java.exitValue(); - - if (code != 0) { - System.out.println(out); - System.err.println(err); - fail("Unexpected exit code: " + code); - } + private static void junit(Path temp, OutputFiles outputFiles) throws Exception { + var projectDir = Path.of("../documentation").toAbsolutePath(); + + var result = ProcessStarters.java() // + .workingDir(projectDir) // + .addArguments("-XX:StartFlightRecording:filename=" + temp.resolve("user-guide.jfr")) // + .addArguments("--show-version", "--show-module-resolution") // + .addArguments("--module-path", String.join(File.pathSeparator, // + temp.resolve("destination").toString(), // + temp.resolve("lib").toString() // + )) // + .addArguments("--add-modules", "documentation") // + .addArguments("--patch-module", "documentation=" + projectDir.resolve("src/test/resources")) // + .addArguments("--module", "org.junit.platform.console") // + .addArguments("execute") // + .addArguments("--scan-modules") // + .addArguments("--config", "enableHttpServer=true") // + .addArguments("--config", LauncherConstants.OUTPUT_DIR_PROPERTY_NAME + "=" + temp.resolve("reports")) // + .addArguments("--fail-if-no-tests") // + .addArguments("--include-classname", ".*Tests") // + .addArguments("--include-classname", ".*Demo") // + .addArguments("--exclude-tag", "exclude") // + .addArguments("--exclude-tag", "exclude") // + .redirectOutput(outputFiles) // + .startAndWait(); + + assertEquals(0, result.exitCode()); } @Test - void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exception { + void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp, + @FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var out = new StringWriter(); var err = new StringWriter(); @@ -182,7 +161,7 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exc // args.forEach(System.out::println); assertTrue(err.toString().isBlank(), () -> err + "\n\n" + String.join("\n", args)); - var listing = Helper.treeWalk(temp); + var listing = treeWalk(temp); assertLinesMatch(List.of( // "destination", // ">> CLASSES >>", // @@ -203,7 +182,32 @@ void runTestsFromUserGuideWithinModularBoundaries(@TempDir Path temp) throws Exc // System.out.println("______________"); // listing.forEach(System.out::println); - junit(temp, out, err); + junit(temp, outputFiles); + } + + private static void loadAllJUnitModules(Path target) throws Exception { + for (var module : loadModuleDirectoryNames()) { + var jar = MavenRepo.jar(module); + Files.copy(jar, target.resolve(jar.getFileName())); + } + } + + private static List treeWalk(Path root) { + var lines = new ArrayList(); + treeWalk(root, lines::add); + return lines; + } + + private static void treeWalk(Path root, Consumer out) { + try (var stream = Files.walk(root)) { + stream.map(root::relativize) // + .map(path -> path.toString().replace('\\', '/')) // + .sorted().filter(Predicate.not(String::isEmpty)) // + .forEach(out); + } + catch (Exception e) { + throw new Error("Walking tree failed: " + root, e); + } } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java index 8ca29012b767..035a4d5fcdae 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/MultiReleaseJarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -14,29 +14,33 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import de.sormuras.bartholdy.Result; - import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.4 */ class MultiReleaseJarTests { - @ResourceLock(Projects.MULTI_RELEASE_JAR) + @ManagedResource + LocalMavenRepo localMavenRepo; + + @ManagedResource + MavenRepoProxy mavenRepoProxy; + @Test - void checkDefault() throws Exception { - var variant = "default"; + void checkDefault(@TempDir Path workspace, @FilePrefix("maven") OutputFiles outputFiles) throws Exception { var expectedLines = List.of( // ">> BANNER >>", // ".", // @@ -67,34 +71,26 @@ void checkDefault() throws Exception { "" // ); - var result = mvn(variant); + var result = ProcessStarters.maven() // + .workingDir(copyToWorkspace(Projects.MULTI_RELEASE_JAR, workspace)) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // + .addArguments("--update-snapshots", "--show-version", "--errors", "--batch-mode") // + .addArguments("test") // + .putEnvironment(MavenEnvVars.forJre(JRE.currentJre())) // + .redirectOutput(outputFiles) // + .startAndWait(); - result.getOutputLines("out").forEach(System.out::println); - result.getOutputLines("err").forEach(System.err::println); + assertEquals(0, result.exitCode()); + assertEquals("", result.stdErr()); - assertEquals(0, result.getExitCode()); - assertEquals("", result.getOutput("err")); - assertTrue(result.getOutputLines("out").contains("[INFO] BUILD SUCCESS")); + var outputLines = result.stdOutLines(); + assertTrue(outputLines.contains("[INFO] BUILD SUCCESS")); + assertFalse(outputLines.contains("[WARNING] "), "Warning marker detected"); + assertFalse(outputLines.contains("[ERROR] "), "Error marker detected"); - var workspace = Path.of("build/test-workspace/multi-release-jar", variant); var actualLines = Files.readAllLines(workspace.resolve("target/junit-platform/console-launcher.out.log")); assertLinesMatch(expectedLines, actualLines); } - private Result mvn(String variant) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setProject(Projects.MULTI_RELEASE_JAR) // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("--update-snapshots", "--show-version", "--errors", "--batch-mode", "--file", variant, - "test") // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - return result; - } - } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java new file mode 100644 index 000000000000..7d31baf23423 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/OutputAttachingExtension.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.MediaType; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.tests.process.OutputFiles; +import org.junit.platform.tests.process.ProcessStarter; + +class OutputAttachingExtension implements ParameterResolver, AfterTestExecutionCallback { + + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( + OutputAttachingExtension.class); + private static final MediaType MEDIA_TYPE = MediaType.create("text", "plain", ProcessStarter.OUTPUT_ENCODING); + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.isAnnotated(FilePrefix.class); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) + throws ParameterResolutionException { + var outputDir = extensionContext.getStore(NAMESPACE).getOrComputeIfAbsent("outputDir", __ -> { + try { + return new OutputDir(Files.createTempDirectory("output")); + } + catch (Exception e) { + throw new ParameterResolutionException("Failed to create temp directory", e); + } + }, OutputDir.class); + var prefix = parameterContext.findAnnotation(FilePrefix.class) // + .map(FilePrefix::value) // + .orElseThrow(); + return outputDir.toOutputFiles(prefix); + } + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + var outputDir = context.getStore(NAMESPACE).get("outputDir", OutputDir.class); + if (outputDir != null) { + try (var stream = Files.list(outputDir.root()).filter(Files::isRegularFile).sorted()) { + stream.filter(OutputAttachingExtension::notEmpty).forEach(file -> { + var fileName = file.getFileName().toString(); + context.publishFile(fileName, MEDIA_TYPE, target -> Files.copy(file, target)); + }); + } + } + } + + private static boolean notEmpty(Path file) { + try { + return Files.size(file) > 0; + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + record OutputDir(Path root) implements ExtensionContext.Store.CloseableResource { + + @Override + public void close() throws Throwable { + try (var stream = Files.walk(root).sorted(Comparator. naturalOrder().reversed())) { + stream.forEach(path -> { + try { + Files.delete(path); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to delete " + path, e); + } + }); + } + } + + private OutputFiles toOutputFiles(String prefix) { + return new OutputFiles(root.resolve(prefix + "-stdout.txt"), root.resolve(prefix + "-stderr.txt")); + } + } +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java deleted file mode 100644 index 9b2126bec212..000000000000 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/PackageCyclesDetectionTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015-2024 the original author or authors. - * - * All rights reserved. This program and the accompanying materials are - * made available under the terms of the Eclipse Public License v2.0 which - * accompanies this distribution and is available at - * - * https://www.eclipse.org/legal/epl-v20.html - */ - -package platform.tooling.support.tests; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import de.sormuras.bartholdy.Configuration; -import de.sormuras.bartholdy.tool.CyclesDetector; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import platform.tooling.support.MavenRepo; - -/** - * @since 1.3 - */ -class PackageCyclesDetectionTests { - - @ParameterizedTest - @MethodSource("platform.tooling.support.Helper#loadModuleDirectoryNames") - @Disabled("Need to pass --module-path...") - void moduleDoesNotContainCyclicPackageReferences(String module) { - var jar = MavenRepo.jar(module); - var result = new CyclesDetector(jar, this::ignore).run(Configuration.of()); - assertEquals(0, result.getExitCode(), "result=" + result); - } - - private boolean ignore(String source, String target) { - if (source.equals(target)) { - return true; - } - if (source.startsWith("org.junit.jupiter.params.shadow.com.univocity.parsers.")) { - return true; - } - //noinspection RedundantIfStatement - if (!target.startsWith("org.junit.")) { - return true; - } - return false; - } - -} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java index 87f8920d16a6..a1fda9669400 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/Projects.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,6 +10,11 @@ package platform.tooling.support.tests; +import java.io.IOException; +import java.nio.file.Path; + +import org.apache.commons.io.file.PathUtils; + public class Projects { public static final String ANT_STARTER = "ant-starter"; @@ -28,4 +33,13 @@ public class Projects { private Projects() { } + + static Path copyToWorkspace(String project, Path workspace) throws IOException { + PathUtils.copyDirectory(getSourceDirectory(project), workspace); + return workspace; + } + + static Path getSourceDirectory(String project) { + return Path.of("projects").resolve(project); + } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java index 273a9a107111..bc9135a1cc54 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ReflectionCompatibilityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -12,45 +12,37 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.platform.tests.process.OutputFiles; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; /** * @since 1.11 */ class ReflectionCompatibilityTests { - @ResourceLock(Projects.REFLECTION_TESTS) @Test - void gradle_wrapper() { - var request = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setProject(Projects.REFLECTION_TESTS) // + void gradle_wrapper(@TempDir Path workspace, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.REFLECTION_TESTS, workspace)) // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache") // - .setTimeout(TOOL_TIMEOUT) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .build(); - - var result = request.run(); - - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - - assertEquals(0, result.getExitCode()); - assertTrue(result.getOutputLines("out").stream().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); - assertThat(result.getOutput("out")).contains("Using Java version: 1.8"); + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .redirectOutput(outputFiles) // + .startAndWait(); + + assertEquals(0, result.exitCode()); + assertTrue(result.stdOut().lines().anyMatch(line -> line.contains("BUILD SUCCESSFUL"))); + assertThat(result.stdOut()).contains("Using Java version: 1.8"); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java index 98f3bb5104a4..6cbb5bc931e1 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/StandaloneTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -15,6 +15,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertLinesMatch; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +import static platform.tooling.support.tests.Projects.copyToWorkspace; +import static platform.tooling.support.tests.Projects.getSourceDirectory; import java.io.File; import java.io.IOException; @@ -25,31 +29,38 @@ import java.util.List; import java.util.stream.Stream; -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.jdk.Jar; -import de.sormuras.bartholdy.jdk.Javac; -import de.sormuras.bartholdy.tool.Java; - +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.platform.tests.process.OutputFiles; +import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; import platform.tooling.support.ThirdPartyJars; /** * @since 1.4 */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Execution(CONCURRENT) class StandaloneTests { - @ResourceLock(Projects.STANDALONE) + @TempDir + static Path workspace; + + @BeforeAll + static void prepareWorkspace() throws IOException { + copyToWorkspace(Projects.STANDALONE, workspace); + } + @Test void jarFileWithoutCompiledModuleDescriptorClass() throws Exception { var jar = MavenRepo.jar("junit-platform-console-standalone"); @@ -66,17 +77,16 @@ void jarFileWithoutCompiledModuleDescriptorClass() throws Exception { assertTrue(found.isEmpty(), jar + " must not contain any " + name + " files: " + found); } - @ResourceLock(Projects.STANDALONE) @Test - void listAllObservableEngines() { - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments("engines", "--disable-ansi-colors", "--disable-banner").build() // - .run(false); + void listAllObservableEngines(@FilePrefix("java") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.java() // + .workingDir(getSourceDirectory(Projects.STANDALONE)) // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // + .addArguments("engines", "--disable-ansi-colors", "--disable-banner") // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); var jupiterVersion = Helper.version("junit-jupiter-engine"); var suiteVersion = Helper.version("junit-platform-suite-engine"); @@ -86,22 +96,20 @@ void listAllObservableEngines() { junit-platform-suite (org.junit.platform:junit-platform-suite-engine:%s) junit-vintage (org.junit.vintage:junit-vintage-engine:%s) """.formatted(jupiterVersion, suiteVersion, vintageVersion).lines(), // - result.getOutput("out").lines()); + result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test - void printVersionViaJar() { - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + void printVersionViaJar(@FilePrefix("java") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.java() // + .workingDir(getSourceDirectory(Projects.STANDALONE)) // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("--version", "--disable-ansi-colors") // .putEnvironment("CLICOLOR_FORCE", "1") // enable ANSI colors by default (see https://picocli.info/#_heuristics_for_enabling_ansi) - .build() // - .run(); + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); var version = Helper.version("junit-platform-console"); assertLinesMatch(""" @@ -109,30 +117,31 @@ void printVersionViaJar() { JVM: .* OS: .* """.formatted(version).lines(), // - result.getOutputLines("out").stream()); + result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test - void printVersionViaModule() { + void printVersionViaModule(@FilePrefix("java") OutputFiles outputFiles) throws Exception { var junitJars = Stream.of("junit-platform-console", "junit-platform-reporting", "junit-platform-engine", "junit-platform-launcher", "junit-platform-commons") // .map(MavenRepo::jar); - var thirdPartyJars = Stream.of(ThirdPartyJars.find("org.opentest4j", "opentest4j")); + var thirdPartyJars = Stream.of( // + ThirdPartyJars.find("org.opentest4j", "opentest4j"), // + ThirdPartyJars.find("org.opentest4j.reporting", "open-test-reporting-tooling-spi") // + ); var modulePath = Stream.concat(junitJars, thirdPartyJars) // .map(String::valueOf) // .collect(joining(File.pathSeparator)); - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // + var result = ProcessStarters.java() // + .workingDir(getSourceDirectory(Projects.STANDALONE)) // .addArguments("--module-path", modulePath) // .addArguments("--module", "org.junit.platform.console") // .addArguments("--version", "--disable-ansi-colors") // .putEnvironment("CLICOLOR_FORCE", "1") // enable ANSI colors by default (see https://picocli.info/#_heuristics_for_enabling_ansi) - .build() // - .run(); + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); var version = Helper.version("junit-platform-console"); assertLinesMatch(""" @@ -140,49 +149,49 @@ void printVersionViaModule() { JVM: .* OS: .* """.formatted(version).lines(), // - result.getOutputLines("out").stream()); + result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(1) - void compile() throws Exception { - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); - var result = Request.builder() // - .setTool(new Javac()) // - .setProject(Projects.STANDALONE) // + @Execution(SAME_THREAD) + void compile(@FilePrefix("javac") OutputFiles javacOutputFiles, @FilePrefix("jar") OutputFiles jarOutputFiles) + throws Exception { + var result = ProcessStarters.javaCommand("javac") // + .workingDir(workspace) // .addArguments("-Xlint:-options") // .addArguments("--release", "8") // .addArguments("-proc:none") // - .addArguments("-d", workspace.resolve("bin")) // - .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone")) // - .addArguments(workspace.resolve("src/standalone/JupiterIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/JupiterParamsIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/SuiteIntegration.java")) // - .addArguments(workspace.resolve("src/standalone/VintageIntegration.java")).build() // - .run(); - - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); - assertTrue(result.getOutput("out").isEmpty()); - assertTrue(result.getOutput("err").isEmpty()); + .addArguments("-d", workspace.resolve("bin").toString()) // + .addArguments("--class-path", MavenRepo.jar("junit-platform-console-standalone").toString()) // + .addArguments(workspace.resolve("src/standalone/JupiterIntegration.java").toString()) // + .addArguments(workspace.resolve("src/standalone/JupiterParamsIntegration.java").toString()) // + .addArguments(workspace.resolve("src/standalone/SuiteIntegration.java").toString()) // + .addArguments(workspace.resolve("src/standalone/VintageIntegration.java").toString()) // + .redirectOutput(javacOutputFiles) // + .startAndWait(); + + assertEquals(0, result.exitCode()); + assertTrue(result.stdOut().isEmpty()); + assertTrue(result.stdErr().isEmpty()); // create "tests.jar" that'll be picked-up by "testWithJarredTestClasses()" later var jarFolder = Files.createDirectories(workspace.resolve("jar")); - var jarResult = Request.builder() // - .setTool(new Jar()) // - .setProject(Projects.STANDALONE) // + var jarResult = ProcessStarters.javaCommand("jar") // + .workingDir(workspace) // .addArguments("--create") // - .addArguments("--file", jarFolder.resolve("tests.jar")) // - .addArguments("-C", workspace.resolve("bin"), ".") // - .build().run(false); - assertEquals(0, jarResult.getExitCode(), String.join("\n", jarResult.getOutputLines("out"))); + .addArguments("--file", jarFolder.resolve("tests.jar").toString()) // + .addArguments("-C", workspace.resolve("bin").toString(), ".") // + .redirectOutput(jarOutputFiles) // + .startAndWait(); + assertEquals(0, jarResult.exitCode()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverTree() { - Result result = discover("--details-theme=ascii"); + @Execution(SAME_THREAD) + void discoverTree(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = discover(outputFiles, "--details-theme=ascii"); var expected = """ . @@ -211,14 +220,14 @@ void discoverTree() { [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverFlat() { - Result result = discover("--details=flat"); + @Execution(SAME_THREAD) + void discoverFlat(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = discover(outputFiles, "--details=flat"); var expected = """ JUnit Platform Suite ([engine:junit-platform-suite]) @@ -246,13 +255,14 @@ JUnit Vintage ([engine:junit-vintage]) [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } @Test @Order(2) - void discoverVerbose() { - Result result = discover("--details=verbose", "--details-theme=ascii"); + @Execution(SAME_THREAD) + void discoverVerbose(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = discover(outputFiles, "--details=verbose", "--details-theme=ascii"); var expected = """ +-- JUnit Platform Suite @@ -330,23 +340,23 @@ void discoverVerbose() { [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverNone() { - Result result = discover("--details=none"); + @Execution(SAME_THREAD) + void discoverNone(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = discover(outputFiles, "--details=none"); - assertThat(result.getOutputLines("out")).isEmpty(); + assertThat(result.stdOut()).isEmpty(); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverSummary() { - Result result = discover("--details=summary"); + @Execution(SAME_THREAD) + void discoverSummary(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = discover(outputFiles, "--details=summary"); var expected = """ @@ -354,14 +364,14 @@ void discoverSummary() { [ 9 tests found ] """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - @ResourceLock(Projects.STANDALONE) @Test @Order(2) - void discoverTestFeed() { - Result result = discover("--details=testfeed"); + @Execution(SAME_THREAD) + void discoverTestFeed(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = discover(outputFiles, "--details=testfeed"); var expected = """ JUnit Platform Suite > SuiteIntegration > JUnit Jupiter > SuiteIntegration$SingleTestContainer > successful() JUnit Jupiter > JupiterIntegration > successful() @@ -378,54 +388,53 @@ JUnit Jupiter > JupiterIntegration > disabled() """.stripIndent(); - assertLinesMatch(expected.lines(), result.getOutputLines("out").stream()); + assertLinesMatch(expected.lines(), result.stdOut().lines()); } - private static Result discover(String... args) { - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + private static ProcessResult discover(OutputFiles outputFiles, String... args) throws Exception { + var result = ProcessStarters.java() // + .workingDir(workspace) // + .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("discover") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // - .addArguments("--disable-ansi-colors") // .addArguments("--include-classname", "standalone.*") // .addArguments("--classpath", "bin") // - .addArguments((Object[]) args) // - .build() // - .run(false); + .addArguments(args) // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(0, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(0, result.exitCode()); return result; } - @ResourceLock(Projects.STANDALONE) @Test @Order(3) - void execute() throws IOException { - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // + @Execution(SAME_THREAD) + void execute(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var result = ProcessStarters.java() // + .workingDir(workspace) // + .putEnvironment("NO_COLOR", "1") // --disable-ansi-colors .addArguments("--show-version") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // - .addArguments("--classpath", "bin").build() // - .run(false); + .addArguments("--classpath", "bin") // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(1, result.exitCode()); - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = Files.readAllLines(workspace.resolve("expected-err.txt")); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - List actualErrLines = result.getOutputLines("err"); + assertLinesMatch(expectedOutLines, result.stdOutLines()); + var actualErrLines = result.stdErrLines(); if (actualErrLines.getFirst().contains("stty: /dev/tty: No such device or address")) { // Happens intermittently on GitHub Actions on Windows actualErrLines = new ArrayList<>(actualErrLines); @@ -435,84 +444,79 @@ void execute() throws IOException { var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" + assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" + assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } - @ResourceLock(Projects.STANDALONE) @Test @Order(4) - void executeOnJava8() throws IOException { - Java java8 = getJava8(); - var result = Request.builder() // - .setTool(java8) // - .setJavaHome(java8.getHome()) // - .setProject(Projects.STANDALONE) // + @Execution(SAME_THREAD) + void executeOnJava8(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); + var result = ProcessStarters.java(java8Home) // + .workingDir(workspace) // .addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--scan-class-path") // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // - .addArguments("--classpath", "bin").build() // - .run(false); + .addArguments("--classpath", "bin") // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(1, result.exitCode()); - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = getExpectedErrLinesOnJava8(workspace); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err")); + assertLinesMatch(expectedOutLines, result.stdOutLines()); + assertLinesMatch(expectedErrLines, result.stdErrLines()); var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" + assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" + assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } - @ResourceLock(Projects.STANDALONE) @Test @Order(5) + @Execution(SAME_THREAD) // https://github.com/junit-team/junit5/issues/2600 - void executeOnJava8SelectPackage() throws IOException { - Java java8 = getJava8(); - var result = Request.builder() // - .setTool(java8) // - .setJavaHome(java8.getHome()) // - .setProject(Projects.STANDALONE) // - .addArguments("-showversion") // + void executeOnJava8SelectPackage(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { + var java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); + var result = ProcessStarters.java(java8Home) // + .workingDir(workspace).addArguments("-showversion") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // .addArguments("-Djunit.platform.launcher.interceptors.enabled=true") // - .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone")) // + .addArguments("-jar", MavenRepo.jar("junit-platform-console-standalone").toString()) // .addArguments("execute") // .addArguments("--select-package", Projects.STANDALONE) // .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // - .addArguments("--classpath", "bin").build() // - .run(false); + .addArguments("--classpath", "bin") // + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); + assertEquals(1, result.exitCode()); - var workspace = Request.WORKSPACE.resolve(Projects.STANDALONE); var expectedOutLines = Files.readAllLines(workspace.resolve("expected-out.txt")); var expectedErrLines = getExpectedErrLinesOnJava8(workspace); - assertLinesMatch(expectedOutLines, result.getOutputLines("out")); - assertLinesMatch(expectedErrLines, result.getOutputLines("err")); + assertLinesMatch(expectedOutLines, result.stdOutLines()); + assertLinesMatch(expectedErrLines, result.stdErrLines()); var jupiterVersion = Helper.version("junit-jupiter-engine"); var vintageVersion = Helper.version("junit-vintage-engine"); - assertTrue(result.getOutput("err").contains("junit-jupiter" + assertTrue(result.stdErr().contains("junit-jupiter" + " (group ID: org.junit.jupiter, artifact ID: junit-jupiter-engine, version: " + jupiterVersion)); - assertTrue(result.getOutput("err").contains("junit-vintage" + assertTrue(result.stdErr().contains("junit-vintage" + " (group ID: org.junit.vintage, artifact ID: junit-vintage-engine, version: " + vintageVersion)); } @@ -523,19 +527,17 @@ private static List getExpectedErrLinesOnJava8(Path workspace) throws IO return expectedErrLines; } - @ResourceLock(Projects.STANDALONE) @Test @Order(6) + @Execution(SAME_THREAD) @Disabled("https://github.com/junit-team/junit5/issues/1724") - void executeWithJarredTestClasses() { + void executeWithJarredTestClasses(@FilePrefix("console-launcher") OutputFiles outputFiles) throws Exception { var jar = MavenRepo.jar("junit-platform-console-standalone"); var path = new ArrayList(); // path.add("bin"); // "exploded" test classes are found, see also test() above - path.add(Request.WORKSPACE.resolve("standalone/jar/tests.jar").toAbsolutePath().toString()); + path.add(workspace.resolve("standalone/jar/tests.jar").toAbsolutePath().toString()); path.add(jar.toString()); - var result = Request.builder() // - .setTool(new Java()) // - .setProject(Projects.STANDALONE) // + var result = ProcessStarters.java() // .addArguments("--show-version") // .addArguments("-enableassertions") // .addArguments("-Djava.util.logging.config.file=logging.properties") // @@ -546,32 +548,9 @@ void executeWithJarredTestClasses() { .addArguments("--disable-banner") // .addArguments("--include-classname", "standalone.*") // .addArguments("--fail-if-no-tests") // - .build() // - .run(false); + .redirectOutput(outputFiles) // + .startAndWait(); - assertEquals(1, result.getExitCode(), () -> getExitCodeMessage(result)); - } - - private static String getExitCodeMessage(Result result) { - return "Exit codes don't match. Stdout:\n" + result.getOutput("out") + // - "\n\nStderr:\n" + result.getOutput("err") + "\n"; - } - - /** - * Special override of class {@link Java} to resolve against a different {@code JAVA_HOME}. - */ - private static Java getJava8() { - Path java8Home = Helper.getJavaHome("8").orElseThrow(TestAbortedException::new); - return new Java() { - @Override - public Path getHome() { - return java8Home; - } - - @Override - public String getVersion() { - return "8"; - } - }; + assertEquals(1, result.exitCode()); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java index d99ab0164ab4..d018c629fc7a 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/ToolProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -31,17 +31,18 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.spi.ToolProvider; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.DisabledOnOpenJ9; +import org.junit.jupiter.api.io.TempDir; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; import platform.tooling.support.ThirdPartyJars; /** @@ -50,12 +51,13 @@ @Order(Integer.MAX_VALUE) class ToolProviderTests { - private static final Path LIB = Request.WORKSPACE.resolve("tool-provider-tests/lib"); + @TempDir + static Path lib; @BeforeAll static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { try { - var lib = Files.createDirectories(LIB); + Files.createDirectories(lib); try (var directoryStream = Files.newDirectoryStream(lib, "*.jar")) { for (Path jarFile : directoryStream) { Files.delete(jarFile); @@ -69,19 +71,28 @@ static void prepareLocalLibraryDirectoryWithJUnitPlatformModules() { } ThirdPartyJars.copy(lib, "org.apiguardian", "apiguardian-api"); ThirdPartyJars.copy(lib, "org.opentest4j", "opentest4j"); + ThirdPartyJars.copy(lib, "org.opentest4j.reporting", "open-test-reporting-tooling-spi"); } catch (Exception e) { throw new AssertionError("Preparing local library folder failed", e); } } + @AfterAll + static void triggerReleaseOfFileHandlesOnWindows() throws Exception { + if (OS.current() == OS.WINDOWS) { + System.gc(); + Thread.sleep(1_000); + } + } + @Test void findAndRunJUnitOnTheClassPath() { - try (var loader = new URLClassLoader("junit", urls(LIB), ClassLoader.getPlatformClassLoader())) { + try (var loader = new URLClassLoader("junit", urls(lib), ClassLoader.getPlatformClassLoader())) { var sl = ServiceLoader.load(ToolProvider.class, loader); var junit = StreamSupport.stream(sl.spliterator(), false).filter(p -> p.name().equals("junit")).findFirst(); - assertTrue(junit.isPresent(), "Tool 'junit' not found in: " + LIB); + assertTrue(junit.isPresent(), "Tool 'junit' not found in: " + lib); assertJUnitPrintsHelpMessage(junit.get()); } catch (IOException e) { @@ -92,12 +103,12 @@ void findAndRunJUnitOnTheClassPath() { @Test @DisabledOnOpenJ9 void findAndRunJUnitOnTheModulePath() { - var finder = ModuleFinder.of(LIB); + var finder = ModuleFinder.of(lib); var modules = finder.findAll().stream() // .map(ModuleReference::descriptor) // .map(ModuleDescriptor::toNameAndVersion) // .sorted() // - .collect(Collectors.toList()); + .toList(); // modules.forEach(System.out::println); var bootLayer = ModuleLayer.boot(); @@ -139,7 +150,7 @@ private static void assertJUnitPrintsHelpMessage(ToolProvider junit) { ">> USAGE >>", // "Launches the JUnit Platform for test discovery and execution.", // ">> OPTIONS >>"), // - out.toString().lines().collect(Collectors.toList())), // + out.toString().lines().toList()), // () -> assertEquals("", err.toString()), // () -> assertEquals(0, code, "Expected exit of 0, but got: " + code) // ); diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java new file mode 100644 index 000000000000..62ca57d144f2 --- /dev/null +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/UnalignedClasspathTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2015-2025 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package platform.tooling.support.tests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +import static platform.tooling.support.ProcessStarters.currentJdkHome; +import static platform.tooling.support.tests.Projects.copyToWorkspace; + +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.platform.tests.process.OutputFiles; + +import platform.tooling.support.Helper; +import platform.tooling.support.MavenRepo; +import platform.tooling.support.ProcessStarters; + +/** + * @since 1.3 + */ +class UnalignedClasspathTests { + + @ManagedResource + LocalMavenRepo localMavenRepo; + + @ManagedResource + MavenRepoProxy mavenRepoProxy; + + @ParameterizedTest + @MethodSource("javaVersions") + @Execution(SAME_THREAD) + void verifyErrorMessageForUnalignedClasspath(JRE jre, Path javaHome, @TempDir Path workspace, + @FilePrefix("maven") OutputFiles outputFiles) throws Exception { + var starter = ProcessStarters.maven(javaHome) // + .workingDir(copyToWorkspace(Projects.MAVEN_STARTER, workspace)) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments("-Dsnapshot.repo.url=" + mavenRepoProxy.getBaseUri()) // + .addArguments("-Djunit.platform.commons.version=1.11.4").addArguments("--update-snapshots", + "--batch-mode", "verify") // + .putEnvironment(MavenEnvVars.forJre(jre)) // + .redirectOutput(outputFiles); + var result = starter.startAndWait(); + + assertEquals(1, result.exitCode()); + assertEquals("", result.stdErr()); + assertTrue(result.stdOutLines().contains("[INFO] BUILD FAILURE")); + assertThat(result.stdOut()) // + .contains("The wrapped NoClassDefFoundError is likely caused by the versions of JUnit jars " + + "on the classpath/module path not being properly aligned"); + } + + static Stream javaVersions() { + return Stream.concat( // + Helper.getJavaHome("8").map(path -> Arguments.of(JRE.JAVA_8, path)).stream(), // + Stream.of(Arguments.of(JRE.currentJre(), currentJdkHome())) // + ); + } +} diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java index b655753eaa10..39a02d21168e 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageGradleIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,63 +11,60 @@ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import java.nio.file.Paths; - -import de.sormuras.bartholdy.Result; -import de.sormuras.bartholdy.tool.GradleWrapper; +import java.nio.file.Path; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.tests.process.OutputFiles; +import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; class VintageGradleIntegrationTests { + @TempDir + Path workspace; + @Test - void unsupportedVersion() { - var result = run("4.11"); + void unsupportedVersion(@FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + var result = run(outputFiles, "4.11"); - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // + assertThat(result.exitCode()).isGreaterThan(0); + assertThat(result.stdOut()) // .doesNotContain("STARTED") // .contains("Unsupported version of junit:junit: 4.11"); } @ParameterizedTest(name = "{0}") @ValueSource(strings = { "4.12", "4.13.2" }) - void supportedVersions(String version) { - var result = run(version); + void supportedVersions(String version, @FilePrefix("gradle") OutputFiles outputFiles) throws Exception { + var result = run(outputFiles, version); - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // + assertThat(result.exitCode()).isGreaterThan(0); + assertThat(result.stdOut()) // .contains("VintageTest > success PASSED") // .contains("VintageTest > failure FAILED"); - var testResultsDir = Request.WORKSPACE.resolve("vintage-gradle-" + version).resolve("build/test-results/test"); + var testResultsDir = workspace.resolve("build/test-results/test"); assertThat(testResultsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); } - private Result run(String version) { - var result = Request.builder() // - .setTool(new GradleWrapper(Paths.get(".."))) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject(Projects.VINTAGE) // - .setWorkspace("vintage-gradle-" + version) // - .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache") // + private ProcessResult run(OutputFiles outputFiles, String version) throws Exception { + return ProcessStarters.gradlew() // + .workingDir(copyToWorkspace(Projects.VINTAGE, workspace)) // + .putEnvironment("JDK8", Helper.getJavaHome("8").orElseThrow(TestAbortedException::new).toString()) // + .addArguments("build", "--no-daemon", "--stacktrace", "--no-build-cache", "--warning-mode=fail") // .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djunit4Version=" + version) // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - return result; + .redirectOutput(outputFiles) // + .startAndWait(); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java index 3358881d600f..2b95c5b891d5 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/VintageMavenIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -11,62 +11,64 @@ package platform.tooling.support.tests; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static platform.tooling.support.Helper.TOOL_TIMEOUT; +import static platform.tooling.support.tests.Projects.copyToWorkspace; -import de.sormuras.bartholdy.Result; +import java.nio.file.Path; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.junit.platform.tests.process.OutputFiles; +import org.junit.platform.tests.process.ProcessResult; import org.opentest4j.TestAbortedException; import platform.tooling.support.Helper; import platform.tooling.support.MavenRepo; -import platform.tooling.support.Request; +import platform.tooling.support.ProcessStarters; class VintageMavenIntegrationTests { + @ManagedResource + LocalMavenRepo localMavenRepo; + + @TempDir + Path workspace; + @Test - void unsupportedVersion() { - var result = run("4.11"); + void unsupportedVersion(@FilePrefix("maven") OutputFiles outputFiles) throws Exception { + var result = run(outputFiles, "4.11"); - assertThat(result.getExitCode()).isEqualTo(0); - assertThat(result.getOutput("out")) // + assertThat(result.exitCode()).isEqualTo(1); + assertThat(result.stdOut()) // + .contains("TestEngine with ID 'junit-vintage' failed to discover tests") // .contains("Tests run: 0, Failures: 0, Errors: 0, Skipped: 0"); } @ParameterizedTest(name = "{0}") @ValueSource(strings = { "4.12", "4.13.2" }) - void supportedVersions(String version) { - var result = run(version); + void supportedVersions(String version, @FilePrefix("maven") OutputFiles outputFiles) throws Exception { + var result = run(outputFiles, version); - assertThat(result.getExitCode()).isGreaterThan(0); - assertThat(result.getOutput("out")) // + assertThat(result.exitCode()).isGreaterThan(0); + assertThat(result.stdOut()) // .contains("Running com.example.vintage.VintageTest") // .contains("VintageTest.failure:") // .contains("Tests run: 2, Failures: 1, Errors: 0, Skipped: 0"); - var surefireReportsDir = Request.WORKSPACE.resolve("vintage-maven-" + version).resolve( - "target/surefire-reports"); + var surefireReportsDir = workspace.resolve("target/surefire-reports"); assertThat(surefireReportsDir.resolve("com.example.vintage.VintageTest.txt")).isRegularFile(); assertThat(surefireReportsDir.resolve("TEST-com.example.vintage.VintageTest.xml")).isRegularFile(); } - private Result run(String version) { - var result = Request.builder() // - .setTool(Request.maven()) // - .setJavaHome(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // - .setProject(Projects.VINTAGE) // - .setWorkspace("vintage-maven-" + version) // + private ProcessResult run(OutputFiles outputFiles, String version) throws Exception { + return ProcessStarters.maven(Helper.getJavaHome("8").orElseThrow(TestAbortedException::new)) // + .workingDir(copyToWorkspace(Projects.VINTAGE, workspace)) // .addArguments("clean", "test", "--update-snapshots", "--batch-mode") // - .addArguments("-Dmaven.repo=" + MavenRepo.dir()) // + .addArguments(localMavenRepo.toCliArgument(), "-Dmaven.repo=" + MavenRepo.dir()) // .addArguments("-Djunit4Version=" + version) // - .setTimeout(TOOL_TIMEOUT) // - .build() // - .run(); - assertFalse(result.isTimedOut(), () -> "tool timed out: " + result); - return result; + .redirectOutput(outputFiles) // + .startAndWait(); } } diff --git a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java index b233d6c0e9eb..6bfd2ff168e1 100644 --- a/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java +++ b/platform-tooling-support-tests/src/test/java/platform/tooling/support/tests/XmlAssertions.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2024 the original author or authors. + * 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 @@ -10,10 +10,10 @@ package platform.tooling.support.tests; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; +import static org.junit.platform.reporting.testutil.FileUtils.findPath; + import java.nio.file.Path; +import java.util.regex.Pattern; import org.xmlunit.assertj3.XmlAssert; import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator; @@ -21,21 +21,18 @@ class XmlAssertions { static void verifyContainsExpectedStartedOpenTestReport(Path testResultsDir) { - try (var files = Files.list(testResultsDir)) { - Path xmlFile = files.filter(it -> it.getFileName().toString().startsWith("junit-platform-events-")) // - .findAny() // - .orElseThrow(() -> new AssertionError("Missing open-test-reporting XML file in " + testResultsDir)); - verifyContent(xmlFile); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } + var xmlFile = findPath(testResultsDir, "glob:**/open-test-report.xml"); + verifyContent(xmlFile); } private static void verifyContent(Path xmlFile) { var expected = """ - ${xmlunit.ignore} @@ -151,7 +148,8 @@ private static void verifyContent(Path xmlFile) { """; XmlAssert.assertThat(xmlFile).and(expected) // - .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) // + .withDifferenceEvaluator(new PlaceholderDifferenceEvaluator(Pattern.quote("${"), Pattern.quote("}"), + Pattern.quote("#"), Pattern.quote("#"), ",")) // .ignoreWhitespace() // .areIdentical(); } diff --git a/platform-tooling-support-tests/src/test/resources/junit-platform.properties b/platform-tooling-support-tests/src/test/resources/junit-platform.properties index 1ddeedc6a798..d24bbed7d3d9 100644 --- a/platform-tooling-support-tests/src/test/resources/junit-platform.properties +++ b/platform-tooling-support-tests/src/test/resources/junit-platform.properties @@ -2,6 +2,9 @@ junit.jupiter.execution.parallel.enabled=true junit.jupiter.execution.parallel.mode.default=concurrent junit.jupiter.execution.parallel.config.strategy=dynamic junit.jupiter.execution.parallel.config.dynamic.factor=0.25 +junit.jupiter.execution.parallel.config.dynamic.max-pool-size-factor=1 junit.jupiter.testclass.order.default = \ org.junit.jupiter.api.ClassOrderer$OrderAnnotation + +junit.jupiter.execution.timeout.default = 3m diff --git a/settings.gradle.kts b/settings.gradle.kts index 9971e3ec5301..ba5ae9fd677f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,11 +15,6 @@ plugins { dependencyResolutionManagement { repositories { mavenCentral() - maven(url = "https://oss.sonatype.org/content/repositories/snapshots") { - mavenContent { - snapshotsOnly() - } - } } } @@ -96,6 +91,7 @@ include("junit-platform-suite-commons") include("junit-platform-suite-engine") include("junit-platform-testkit") include("junit-vintage-engine") +include("jupiter-tests") include("platform-tests") include("platform-tooling-support-tests") include("junit-bom")